[ProJect 일지] 키오스크 (3) (Compositional Layout)

2024.04.02 - [◽️ Programming/T I L] - [ProJect 일지] 키오스크 (1)

2024.04.03 - [◽️ Programming/T I L] - [ProJect 일지] 키오스크 (2)


어제에 이어서 가로 컬렉션 뷰에 헤더를 넣고 홈 화면을 구현하는 것 까지가 목표였지만 홈화면을 내 생각보다 많이 타협해서 완성한 뒤 고객센터 탭까지 구현에 완료 하였다 🙂

그래도 이제 테이블 뷰와 같이 해본 작업을 또 다시 해보니 생각보다 속도가 더 나와 뿌듯한 하루 였다.

그럼 오늘 구현한 내용을 한번 보자!

 

먼저 컬렉션 뷰를 가로로 배치하여 스와이프를 진행하면 당연히 헤더도 내가 생각한 위에 위치할거라고 생각했다.

아무리 맞는 것 같은 코딩을 넣어도 헤더가 위에 붙는게 아니라 제일 앞에 붙어서 이거 해결하느라 굉장히 많은 시간을 쏟았다..

결국 튜터님을 찾아간 결과.. 컬렉션 뷰를 가로로 구성하고 헤더를 넣으면 지금처럼 앞쪽에 붙어서 나오는게 정상..

 

내가 원하는 것 처럼 구현하려면 Compositional Layout 을 사용 해야 한다고 말씀해 주셨다..

그래서 부랴부랴 Compositional 에 대해서 배우게 되었고 일단 간단하게 알아보자

 

Compositional Layout

Compositional Layout 은 iOS 13 부터 도입된 UICollectionViewLayout 새로운 형태라고 한다.

컬렉션 뷰의 아이템 배치를 정의하며 이를 사용하면 코드를 통해 다양한 레이아웃을 정의하고 동적으로 조정할 수 있다.

UICollectionViewLayout 핵심 요소

  • NSCollectionLayoutItem : 컬렉션 뷰에서 표시되는 개별 아이템을 정의한다. 크기와 다양한 속성을 설정할 수 있다.
  • NSCollectionLayoutGroup : 아이템들을 그룹화하여 레이아웃을 구성한다. 수평 또는 수직으로 배열하며 그룹 안에는 다른 그룹이나 아이템을 포함할 수 있다.
  • NSCollectionLayoutSection : 컬렉션 뷰의 섹션을 정의한다. 섹션에는 아이템, 그룹, 보충 요소 등을 포함 할 수 있다.
  • NSCollectionLayoutSupplementaryItem : 섹션에 추가되는 보충 요소를 정의한다. 헤더나 푸터 등을 추가할 수 있다.

이러한 내용을 토대로 나는 내가 구성한 컬렉션 뷰에 헤더를 넣을 수 있었다.

let layout = UICollectionViewCompositionalLayout { (sectionIndex, _) -> NSCollectionLayoutSection? in
    let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.1))
    let item = NSCollectionLayoutItem(layoutSize: itemSize)
    item.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8)
    
    let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.9), heightDimension: .absolute(500))
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
    
    let section = NSCollectionLayoutSection(group: group)
    section.orthogonalScrollingBehavior = .groupPaging
    section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
    
    let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(50))
    let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
    header.contentInsets = NSDirectionalEdgeInsets(top: 20, leading: 24, bottom: 0, trailing: 0)
    section.boundarySupplementaryItems = [header]

원래는 여러가지 동일하게 구성 된 컬렉션 뷰를 나열해 세로로도 길게 구성된 전체 뷰를 만들고 싶었지만 같은 컬렉션을 구현하는데 너무 오랜 시간이 걸려 차라리 컬렉션 뷰의 크기를 키우고 하나의 컬렉션 뷰로 구성되도록 수정하게 되었다..

좀 아쉬웠지만 나중에 Compositional Layout 을 더 공부해서 꼭 다시 구현해봐야지..

이렇게 완성된 내용을 넣어보니 그럴싸하게 보이는 것 같아 그나마 다행이었다..

더이상 여기에 시간을 더 투자할 순 없으니 일단 이렇게 마무리하고 다른 추가 기능을 구현해보자!

TabelView 를 활용한 고객센터 페이지 구현

프로젝트의 탭 중 하나를 차지할 고객센터 페이지를 구현하기로 해서 원래 사용했던 테이블 뷰를 사용해서 후딱 구현하기로 했다.

먼저 데이터 모델링을 잡아주자

struct Menu {
    let title: String
    let leftImage: String?
    let rightImage: String?
}

그 이후 테이블 뷰를 코드로 구현하는 연습을 하기 위해 코드로 구현했다!

class CallCenterTableViewCell: UITableViewCell {
    let leftImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFit
        return imageView
    }()
    
    let mainLabel: UILabel = {
        let label = UILabel()
        label.font = UIFont.systemFont(ofSize: 17)
        return label
    }()
    
    let rightImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFit
        return imageView
    }()
    
    let stackView: UIStackView = {
        let sv = UIStackView()
        sv.axis = .horizontal
        sv.distribution = .fill
        sv.alignment = .fill
        sv.spacing = 15
        return sv
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupSubviews()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
// MARK: - 테이블 뷰 오토 레이아웃
    private func setupSubviews() {
        contentView.addSubview(stackView)
        stackView.addArrangedSubview(leftImageView)
        stackView.addArrangedSubview(mainLabel)
        stackView.addArrangedSubview(rightImageView)
        
        stackView.translatesAutoresizingMaskIntoConstraints = false
        leftImageView.translatesAutoresizingMaskIntoConstraints = false
        mainLabel.translatesAutoresizingMaskIntoConstraints = false
        rightImageView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
            stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8),
            stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8),
            stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
            
            leftImageView.widthAnchor.constraint(equalToConstant: 20),
            leftImageView.heightAnchor.constraint(equalToConstant: 20),
            
            rightImageView.widthAnchor.constraint(equalToConstant: 20),
            rightImageView.heightAnchor.constraint(equalToConstant: 20),
        ])
    }
}

그 이후 셀 내 데이터를 넣어주는 함수를 만들어주고

 func setupData() {
    callCenter = [
        Menu(title: "매장찾기", leftImage: "checkmark.circle", rightImage: "greaterthan.circle"),
        Menu(title: "마이 페이지", leftImage: "person.circle", rightImage: "greaterthan.circle"),
        Menu(title: "이용 약관", leftImage: "list.bullet.rectangle", rightImage: "greaterthan.circle"),
        Menu(title: "개인정보 처리 방침", leftImage: "list.bullet.rectangle", rightImage: "greaterthan.circle"),
        Menu(title: "고객문의", leftImage: "phone.fill", rightImage: "greaterthan.circle"),
        Menu(title: "자주 묻는 질문", leftImage: "tray.2.fill", rightImage: "greaterthan.circle"),
        Menu(title: "국가/언어 변경", leftImage: "globe.asia.australia.fill", rightImage: "greaterthan.circle")
    ]
    tableView.reloadData()
}

네비게이션 바의 옵션을 설정 하는 코드를 구현해줬다.

 func setupNaviBar() {
    title = "고객센터"
    
    let appearance = UINavigationBarAppearance()
    appearance.configureWithOpaqueBackground()
    appearance.backgroundColor = .white
    navigationController?.navigationBar.tintColor = .black
    navigationController?.navigationBar.standardAppearance = appearance
    navigationController?.navigationBar.compactAppearance = appearance
    navigationController?.navigationBar.scrollEdgeAppearance = appearance
    navigationController?.navigationBar.prefersLargeTitles = true
}

func setupTableView() {
    tableView.dataSource = self
    tableView.delegate = self
    tableView.rowHeight = 90
    tableView.register(CallCenterTableViewCell.self, forCellReuseIdentifier: "CallCenterCell")
}

그리고 델리게이트 패턴 및 데이터 소스를 활용했기 때문에 필수 함수를 구현했다.

extension CallCenterViewController: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return callCenter.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CallCenterCell", for: indexPath) as! CallCenterTableViewCell
        let menu = callCenter[indexPath.row]
        cell.selectionStyle = .none
        
        //이미지 블랙으로 변경
        cell.mainLabel.text = menu.title
        if let leftImageName = menu.leftImage {
            cell.leftImageView.image = UIImage(systemName: leftImageName)?.withTintColor(.black, renderingMode: .alwaysOriginal)
        } else {
            cell.leftImageView.image = nil
        }
        
        if let rightImageName = menu.rightImage {
            cell.rightImageView.image = UIImage(systemName: rightImageName)?.withTintColor(.black, renderingMode: .alwaysOriginal)
        } else {
            cell.rightImageView.image = nil
        }
        
        
        return cell
    }
}

이 외에 네비게이션 바를 코드로 구현하기 위해 SceneDelegate 파일에 이렇게 코드로 구현했지만

이게 다른 파일과 합쳐졌을때 문제가 발생한다는 점을 인지하지 못했다.. 정말 새로운 사실..

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let windowScene = (scene as? UIWindowScene) else { return }
    window = UIWindow(windowScene: windowScene)
    
    let naviVC = UINavigationController(rootViewController: CallCenterViewController())
    
    window?.rootViewController = naviVC
    window?.makeKeyAndVisible()

}

 

이렇게 네비게이션바를 코드로 구현했을때 생기는 문제점은 메인 프로젝트 파일에 내가 만든 고객센터 탭 데이터를 옮기고 SceneDelegate 파일에 네비게이션 코드 구현된 내용을 넣으니 빌드 됐을때 프로젝트 앱이 보이는게 아니라 고객센터 탭만 로드가 되는 문제가 발생했다..

그래서 해당 내용은 지우고 네비게이션 바만 스토리보드에 구현한 후 코드와 섞어 사용하니 문제가 해결되어 새로운 사실을 알게 되었고 해결 방법도 깨달았다..

오늘은 어제보다는 더 구현에 성공한 것 같아 좋았지만 한편으로 원했던 내용을 끝까지 구현하지 못해 아쉽기도 하다 다음에는 꼭 다 완수할 수 있도록 하고 Compositional Layout 공부하자

 

지금 까지 구현 된 우리 프로젝트 결과물 >