# UIKit 4 리스트 & 그리드 Deep Dive

이은호·2023년 2월 22일
0

swift

목록 보기
5/8

콜렉션뷰로 작업을 하다보면 복잡한 구현은 힘들겠다는 생각이 들수도 있다.
새로운 방식으로 date,presentation,layout을 바꿔야한다.

import UIKit

class FrameworkListViewController: UIViewController {
    
    @IBOutlet weak var collectionView: UICollectionView!
    
    let list: [AppleFramework] = AppleFramework.list
    
    var datasource: UICollectionViewDiffableDataSource<Section,Item>!
    
    typealias Item = AppleFramework
    enum Section {
        case main
    }
    // Data, Presentation, Layout
    override func viewDidLoad() {
        super.viewDidLoad()
        

        collectionView.delegate = self
        navigationController?.navigationBar.topItem?.title = "☀️ Apple Frameworks"
        
//        diffable datasource
//        - presentation
        datasource = UICollectionViewDiffableDataSource<Section,Item>(collectionView: collectionView, cellProvider: { collectionView, indexPath, item in
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FrameworkCell", for: indexPath) as? FrameworkCell else{
                return nil
            }
            cell.configure(item)
            return cell
        })
//        snapshot
//        - data
        var snapshot = NSDiffableDataSourceSnapshot<Section,Item>()
        snapshot.appendSections([.main])
        snapshot.appendItems(list,toSection: .main)
        datasource.apply(snapshot)
//        compositional layout
//        - layout
        collectionView.collectionViewLayout = layout()
        
    }
    private func layout() -> UICollectionViewCompositionalLayout{
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.33), heightDimension: .fractionalHeight(1))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(0.33))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 3)
        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 20)
        let layout = UICollectionViewCompositionalLayout(section: section)
        return layout
    }
}

extension FrameworkListViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let framework = list[indexPath.item]
        print(">>> selected: \(framework.name)")
    }
}

뭐가 바뀐지는 알 수 없지만 코드의 형식이 더 복잡해졌고, 복잡하게 개발함으로써 복잡한 구현도 할 수 있다는 것을 알면 되겠다.

예제1

이전에 쓰던 방식으로는 모든 셀들이 같은 형태를 유지하지만 이번 예제는 각 셀마다 서로 다른 디자인들이 첨가되어 있다.

렌더링 에러

또 오토레이아웃관련 문제이다. 요소들과의 관계가 = 이 아니라 >=등으로 설정해줌으로써 파훼했다. 이렇게 되면 스토리보드 자체는 불평하지만 실제로 가동할때는 문제가 없다. 이렇게 코드함으로써, 각 셀마다 설명이 긴 셀이라면 더 길게 보여주는 것을 볼 수 있다.
참고로 라인수를 0으로 지정해주면 무한으로 늘어난다.

런타임 에러

uiImage(name)을 UIImage(systemName)으로 변경해주었다.

이미지 에러

이미지를 멀티컬러로 저장했음에도 보이지 않는다. 디폴트 값을 변경해주어야한다.

func configure(_ item: Focus){
        titleLabel.text = item.title
        descriptionLabel.text = item.description
        thumnailImageView.image = UIImage(systemName: item.imageName)?.withRenderingMode(.alwaysOriginal)
    }

.alwaysTemplate가 디폴트 값이다.

리스트 펼치기 접기

//
//  FocusViewController.swift
//  HeadSpaceFocus
//
//  Created by 이은호 on 2023/02/21.
//

import UIKit

class FocusViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!
    
    var items:[Focus] = Focus.list
    
    typealias Item = Focus
    
    enum Section{
        case main
    }
    
    @IBOutlet weak var moreBtn: UIButton!
    
    var isAll:Bool = false;
    
    
    var datasource: UICollectionViewDiffableDataSource<Section,Item>!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        datasource = UICollectionViewDiffableDataSource<Section,Item>(collectionView: collectionView, cellProvider: { collectionView, indexPath, item in
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FocusCell", for: indexPath) as? FocusCell else{
                return nil
            }
            cell.configure(item)
            return cell
        })
        
        // Data: Snapshot
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.main])
        snapshot.appendItems(items, toSection: .main)
        datasource.apply(snapshot)
        
        // Layout
        collectionView.collectionViewLayout = layout()
        
        let title = isAll ? "더 많은 날씨 보기":"접기"
        moreBtn.setTitle(title, for: .normal)
        
    }
    private func layout() -> UICollectionViewCompositionalLayout {
        
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(50))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(50))
        let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
        
        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20)
        section.interGroupSpacing = 10
        
        let layout = UICollectionViewCompositionalLayout(section: section)
        return layout
    }
    
    @IBAction func btnToggle(_ sender: Any) {
        isAll.toggle()
        self.items = isAll ? Focus.recommendations : Focus.list
        var snapshot = NSDiffableDataSourceSnapshot<Section,Item>()
        snapshot.appendSections([.main])
        snapshot.appendItems(items,toSection: .main)
        datasource.apply(snapshot)
        let title = isAll ? "더 많은 날씨 보기":"접기"
        moreBtn.setTitle(title, for: .normal)
    }
}

버튼과 뷰의 상호작용에 대한 코드를 작성해주면 그만이다. 애니메이션은 자동으로 생성된다 휴~

예제 둘

이번에는 가로스크롤뷰와 세로스크롤뷰가 섞여있는 앱을 만들어보도록 하자.
ㅇ아오 또 날라갔다 중요한점은 온보딩페이징뷰와 다르게 작업한다는 것이다.


import UIKit
class PaywallViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!
    @IBOutlet weak var pageControl: UIPageControl!
    
    let bannerInfos: [BannerInfo] = BannerInfo.list
    let colors: [UIColor] = [.systemPurple, .systemOrange, .systemPink, .systemRed]
    
    enum Section {
        case main
    }
    typealias Item = BannerInfo
    var datasource: UICollectionViewDiffableDataSource<Section, Item>!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Presentation
        datasource = UICollectionViewDiffableDataSource<Section, Item>.init(collectionView: collectionView, cellProvider: { collectionView, indexPath, item in
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BannerCell", for: indexPath) as? BannerCell else {
                return nil
            }
            cell.configure(item)
            cell.backgroundColor = self.colors[indexPath.item]
            return cell
        })
        
        // Data
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.main])
        snapshot.appendItems(bannerInfos, toSection: .main)
        datasource.apply(snapshot)
        
        // layout
        collectionView.collectionViewLayout = layout()
        collectionView.alwaysBounceVertical = false
        
        self.pageControl.numberOfPages = bannerInfos.count
    }
    
    private func layout() -> UICollectionViewCompositionalLayout {
        
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.8), heightDimension: .absolute(200))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .groupPagingCentered
        section.interGroupSpacing = 20
        
        section.visibleItemsInvalidationHandler = { (items, offset, env) in
            let index = Int((offset.x / env.container.contentSize.width).rounded(.up))
            print("--> \(index)")
            self.pageControl.currentPage = index
        }

        let layout = UICollectionViewCompositionalLayout(section: section)
        return layout
    }
}

앞으로 리스트,그리드뷰를 만들일이 있다면

[https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/implementing_modern_collection_views] 여기에 수많은 예제들이 있음으로 참고해서 작성해주면된다.

부록

주석

코드를 선택한후 커맨드+? 를 입력한다.

Hashable

기본적으로 선형탐색과 해시테이블의 성능차이가 어마어마한것은 알것이다. 그리고 스위프트는 자료구조를 반 드 시 해시형으로 선언해야 하는 경우가 있다.
참고

0개의 댓글