IGListKit

IGListKit là một trong những thư viện mã nguồn mở IOS phổ biến nhất trong năm 2017.

Được phát triển bởi Instagram, IGListKit là khung UICollectionView dựa vào dữ liệu để xây dựng các danh sách nhanhlinh hoạt. Nó cung cấp nhiều tính năng khác như hình động và các tính toán phức tạp làm cho các ứng dụng của chúng ta trông ưa nhìn và nhanh hơn. Xem hướng dẫn từ Ray Wenderlich về việc triển khai thư viện này trong ứng dụng của bạn.

IGListKit tự động diffs các đối tượng của bạn và thực hiện cập nhật hàng loạt animations trên UICollectionView cho bất cứ điều gì thay đổi. Bằng cách này bạn không bao giờ phải tự bản sửa lỗi hàng loạt.

Mô tả

Trong khi UICollectionViewUITableView là một công cụ vô cùng mạnh mẽ, với sức mạnh to lớn đến trách nhiệm to lớn. Giữ nguồn dữ liệu của bạn và chế độ xem đồng bộ có tầm quan trọng vô cùng, và sự cố thường gây ra do ngắt kết nối ở đây.

IGListKit là một khung UICollectionView dựa vào dữ liệu được xây dựng bởi nhóm nghiên cứu tại Instagram. Với framework này, bạn cung cấp một mảng các đối tượng để hiển thị trong UICollectionView. Đối với mỗi loại đối tượng bộ điều hợp tạo một cái gì đó gọi là section controller, có tất cả các chi tiết để tạo các cells.

Ứng dụng của IGListKit

Chúng ta sẽ sử dụng IGListKit trên một ứng dụng sử dụng một UITableView, chúng ta sẽ sử dụng Marvel App của Thiago Lioy. Bạn có thể tìm hiểm về app tại link.

Trước hết chúng ta cần cài đặt IGListKit. Điều đó có thể được thực hiện thông qua cocoapods hoặc carthage. Sử dụng cocoapods bạn sẽ điền đoạn mã trong file pod của bạn:

pod 'IGListKit', :git => 'https://github.com/Instagram/IGListKit.git', :branch => 'master'

Trong ứng dụng Marvel App tại CharactersViewController, chúng ta có thể thấy một TableView và nó là delegate và datasource sử dụng một mảng Character.

Bây giờ chúng ta có thể bắt đầu bằng cách thay đổi Character model. Các models phải phù hợp với IGListDiffable protocol. Chúng ta có thể làm điều đó bằng cách sử dụng các extension:



extension Character: Equatable {
    static public func ==(rhs: Character, lhs: Character) -> Bool {
        return rhs.id == lhs.id
    }
}

extension Character: IGListDiffable {
    
    public func diffIdentifier() -> NSObjectProtocol {
        return NSNumber(value: id)
    }
    
    public func isEqual(toDiffableObject object: IGListDiffable?) -> Bool {
        guard let object = object as? Character else {
            return false
        }
        
        if id != object.id || name != object.name {
            return false
        }
        
        return self == object
    }
}

Bây giờ hãy tạo một UIViewController mới trên storyboard. Thêm một UIView và sau đó thay đổi nó là class IGListCollectionView:

IGListKit sử dụng một khái niệm thú vị về bộ chuyển đổi làm phá vỡ các đối tượng trong các phần riêng biệt được gọi là SectionControllers. Mỗi SectionControllerresponsable cho một phần có thể có nhiều cells, nhưng trong ví dụ này chúng ta sẽ chỉ có một cell.

Bây giờ chúng ta có thể tạo một UIViewController mới gọi là tableCharacterViewController.swift. Trong tệp này chúng ta có IGListCollectionView, một IGListAdapter và thực hiện IGListAdapterDataSource.


import UIKit
import IGListKit

class tableCharacterViewController: UIViewController {
    
    @IBOutlet weak var collectionView: IGListCollectionView!
    
    var apiManager: MarvelAPICalls = MarvelAPIManager()
    var characters: [Character] = []
    
    lazy var adapter: IGListAdapter = {
        return IGListAdapter(updater: IGListAdapterUpdater(), viewController: self, workingRangeSize: 0)
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.collectionView.collectionViewLayout = UICollectionViewFlowLayout()
        
        fetchCharacters()
        
        adapter.collectionView = collectionView
        adapter.dataSource = self
    }
    
    func fetchCharacters(for query: String? = nil) {
        apiManager.characters(query: query, skip: self.characters.count) { characters in
            if let characters = characters {
                self.characters += characters
                self.adapter.performUpdates(animated: true)
            }
        }
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        collectionView.frame = view.bounds
    }
    
    func showDetailsOf(character: Character) {
        guard let nextController = Storyboard.Main.characterViewControllerScene
            .viewController() as? CharacterViewController else {
                return
        }
        
        nextController.character = character
        self.navigationController?.pushViewController(nextController, animated: true)
    }
}

extension tableCharacterViewController: IGListAdapterDataSource {
    
    func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] {
        return self.characters
    }
    
    func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController {
        
        print((object as! Character).name)
        return TableSectionController()
    }
    
    func emptyView(for listAdapter: IGListAdapter) -> UIView? {
        let view = UIView()
        view.backgroundColor = .lightGray
        return view
    }
}

IGListAdapter có một IGListUpdatingDelegate xử lý các sự kiện cập nhật của hàng và các phần. IGListKit cung cấp IGListAdapterUpdater như một triển khai cụ thể.

IGListAdapterDataSource đưa ra ba phương pháp để thực hiện. Một yêu cầu về kích thước của collection view, yêu cầu khác là section controller của một đối tượng nhất định và cuối cùng yêu cầu một chế độ xem trống sẽ hiển thị khi một collection đã cho không có đối tượng nào để hiển thị.

Chúng ta chỉ phải làm thêm hai việc nữa: khởi tạo SectionControllerCollectionViewCell.

The SectionController là một loại delegate. Nó sẽ xử lý các dữ liệu sẽ được thông qua bởi các bộ chuyển đổi trên didUpdate phương pháp và trả về cell tùy chỉnh của chúng ta trên phương thức cellForIndex .


import UIKit
import IGListKit
import Reusable

class TableSectionController: IGListSectionController {
    var character: Character?
}

extension TableSectionController: IGListSectionType {
    func numberOfItems() -> Int {
        return 1
    }
    
    func sizeForItem(at index: Int) -> CGSize {
        return CGSize(width: 375, height: 80)
    }
    
    func cellForItem(at index: Int) -> UICollectionViewCell {
        let cell = collectionContext!.dequeueReusableCell(withNibName: PictureCollectionCell.reuseIdentifier, bundle: Bundle.main, for: self, at: index)
        
        if let cell = cell as? Charactable, let character = self.character{
            cell.setup(character: character)
        }
        
        return cell
    }
    
    func didUpdate(to object: Any) {
        character = object as? Character
    }
    
    func didSelectItem(at index: Int) {
        if let vc = self.viewController as? tableCharacterViewController, let character = self.character {
            vc.showDetailsOf(character: character)
        }
    }
}

Cuối cùng tùy chỉnh cell là UICollectionViewCell:


import UIKit
import Reusable

protocol Charactable {
    func setup(character: Character)
}

class PictureCollectionCell: UICollectionViewCell, NibReusable, Charactable {

    @IBOutlet weak var name: UILabel!
    @IBOutlet weak var characterDescription: UILabel!
    @IBOutlet weak var picture: UIImageView!
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }
    
    func setup(character: Character) {
        if let thumbImage = character.thumbImage {
            self.picture.download(image: (thumbImage.fullPath()))
        }
        name.text = character.name
        characterDescription.text = character.bio.isEmpty ? "No description" : character.bio
    }
}

Trên đây là các bước bạn có thể thay đổi bạn UITableView thành IGListCollectionView và có thể sử dụng hình động và tính toán khác.

Bạn có thể tìm thêm ví dụ về IGListKit tại githubraywanderlich.com.

Nguồn

Dịch theo tác giả Rodrigo Cavalcante.