[swift] Swift IGListKit에서 컬렉션 뷰 페이지네이션 구현하기

IGListKit은 iOS 애플리케이션에서 복잡한 컬렉션 뷰 레이아웃을 관리하기 위한 강력한 라이브러리입니다. IGListKit은 UICollectionView의 데이터 공급자에 의해 제공되는 섹션화된 데이터를 사용하여 컬렉션 뷰를 빠르게 업데이트 할 수 있습니다.

이번 튜토리얼에서는 IGListKit을 사용하여 컬렉션 뷰 페이지네이션을 구현하는 방법에 대해 살펴보겠습니다. 페이지네이션은 한 번에 표시되는 아이템 수를 제한하고 스크롤이 끝에 도달하면 다음 페이지를 로드하는 기능입니다.

IGListKit 및 IGListKit_diffing 설치하기

먼저, IGListKit 및 IGListKit_diffing을 설치해야 합니다. 이를 위해 CocoaPods를 사용할 수 있습니다.

$ pod install

데이터 모델 및 데이터 공급자 설정하기

페이지네이션을 구현하기 위해 먼저 데이터 모델과 데이터 공급자를 설정해야 합니다. 예를 들어, Post 모델을 사용하고, PostDataProvider 라는 데이터 공급자를 만들어서 게시물 데이터를 가져올 수 있습니다.

// Post 모델 정의
struct Post {
    let id: String
    let text: String
    let imageURL: URL
}

// 데이터 공급자 클래스 정의
class PostDataProvider {
    // 게시물 목록을 가져오는 메소드
    func fetchPosts(page: Int, completion: @escaping ([Post]) -> Void) {
        // 실제로 서버에서 해당 페이지의 게시물 데이터를 가져온다면 여기서 비동기 호출을 수행한다.
        // 이 예시에서는 간단히 예시 데이터를 반환한다.
        let posts = [
            Post(id: "1", text: "First post", imageURL: URL(string: "https://example.com/1.jpg")!),
            Post(id: "2", text: "Second post", imageURL: URL(string: "https://example.com/2.jpg")!)
        ]
        completion(posts)
    }
}

위의 예시에서는 fetchPosts(page:completion:) 메소드를 호출하여 해당 페이지의 게시물 데이터를 가져오도록 구현하였습니다.

IGListKit 데이터 소스 설정하기

이제 IGListKit 데이터 소스를 설정할 차례입니다. IGListKit은 컬렉션 뷰의 데이터를 관리하기 위해 데이터 소스를 사용합니다. IGListKit 데이터 소스는 섹션과 아이템의 정보를 제공하여 컬렉션 뷰를 업데이트합니다.

class PostDataSource: NSObject, ListAdapterDataSource {
    // IGListKit 데이터 소스 메소드 구현
    
    func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
        // IGListKit에 전달할 섹션화된 Diffable 객체를 반환하는 메소드
        // 여기서는 페이지별로 프로세싱이 필요하므로 빈 배열을 반환한다.
        return []
    }
    
    func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
        // 섹션 컨트롤러를 반환하는 메소드
        // 여기서는 페이지별 섹션 컨트롤러를 반환한다.
        return PostSectionController()
    }
    
    func emptyView(for listAdapter: ListAdapter) -> UIView? {
        // 데이터가 없을 때 보여줄 뷰를 반환하는 메소드
        return nil
    }
}

위의 예시에서는 IGListKit 데이터 소스를 구현하기 위해 NSObject를 상속하고, ListAdapterDataSource 프로토콜을 채택하여 필요한 메소드를 구현하였습니다. 현재는 objects(for:), listAdapter(_:sectionControllerFor:), emptyView(for:) 메소드를 구현하고 있습니다.

섹션 컨트롤러 설정하기

마지막으로 섹션 컨트롤러를 설정해야 합니다. 섹션 컨트롤러는 IGListKit 내부에서 각각의 섹션을 관리하는 역할을 합니다. 페이지 번호에 따라 필요한 데이터를 가져와 컬렉션 뷰에 바인딩하는 역할을 수행합니다.

class PostSectionController: ListSectionController {
    private var posts: [Post] = []
    private var loading = false
    private var pageNumber = 1
    
    override init() {
        super.init()
        
        // 섹션 컨트롤러 등록
        self.supplementaryViewSource = self
        self.inset = UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 0) // 섹션 간의 간격 설정
    }
    
    override func didUpdate(to object: Any) {
        // 섹션 컨트롤러가 업데이트되면 호출되는 메소드
        if let object = object as? Int {
            fetchPosts(page: object)
        }
    }
    
    // 게시물 데이터 가져오기
    private func fetchPosts(page: Int) {
        guard !loading else { return }
        loading = true
        
        PostDataProvider().fetchPosts(page: page) { [weak self] posts in
            guard let self = self else { return }
            self.posts += posts
            self.loading = false
            
            // IGListKit에 데이터 업데이트를 알린다.
            self.adapter?.performUpdates(animated: true, completion: nil)
        }
    }
}

// 섹션의 보조 뷰를 설정하기 위한 프로토콜 구현
extension PostSectionController: ListSupplementaryViewSource {
    func supportedElementKinds() -> [String] {
        return [UICollectionView.elementKindSectionHeader]
    }
    
    func viewForSupplementaryElement(ofKind elementKind: String, at index: Int) -> UICollectionReusableView {
        // 섹션의 헤더 뷰 구현
        // 여기서는 헤더 뷰를 사용하지 않으므로 빈 UICollectionReusableView를 반환한다.
        return UICollectionReusableView()
    }
}

위의 예시에서는 PostSectionController 클래스를 구현하여 IGListKit의 ListSectionController를 상속합니다. didUpdate(to:) 메소드는 섹션 컨트롤러가 업데이트되면 호출되는 메소드로, 여기에서 게시물 데이터를 가져오도록 구현되어 있습니다.

마지막으로, 섹션 컨트롤러에서는 ListSupplementaryViewSource 프로토콜을 구현하여 섹션의 보조 뷰를 설정할 수 있습니다. 예를 들어, 이 프로토콜을 사용하여 섹션의 헤더나 푸터 뷰를 추가할 수 있습니다.

IGListKit 사용하기

이제 모든 설정이 준비되었으므로 IGListKit을 사용하여 컬렉션 뷰 페이지네이션을 구현할 수 있습니다.

class ViewController: UIViewController {
    private let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
    private var adapter: ListAdapter!
    private var page = 1
    
    private let postDataSource = PostDataSource()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        collectionView.backgroundColor = .white
        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        
        view.addSubview(collectionView)
        
        adapter = ListAdapter(updater: ListAdapterUpdater(), viewController: self)
        adapter.collectionView = collectionView
        adapter.dataSource = postDataSource
        
        // 최초 데이터 로드
        fetchData()
    }
    
    // 데이터 가져오기
    private func fetchData() {
        postDataSource.pageNumber = page
        
        // IGListKit에 섹션 업데이트를 알린다.
        adapter.performUpdates(animated: true, completion: nil)
        
        page += 1
    }
}

위의 예시에서는 ViewController에 IGListKit의 ListAdapter를 설정하여 컬렉션 뷰와 연결하고, postDataSource를 데이터 소스로 설정합니다. fetchData() 메소드를 호출하여 초기 데이터를 가져와 섹션 업데이트를 알립니다.

이제 IGListKit을 사용하여 컬렉션 뷰 페이지네이션을 구현할 수 있습니다. IGListKit을 사용하면 복잡한 데이터 관리를 할 때 간편하게 처리할 수 있으므로, 대규모의 컬렉션 뷰를 다루는 애플리케이션에 유용합니다.

참고: IGListKit 공식 문서 - IGListKit