Làm gọn khai báo table view

Tản mạn

Table view là view được sử dụng nhiều nhất trong lập trình iOS. Khi chúng ta sử dụng table view và dùng custom cell, chúng ta sẽ có hai quá trình khai báo chính.

  • Khai báo cell custom với table
  • Lấy cell được khởi tạo từ hệ thống đế hiển thị Hai quá trình này lập đi lập lại và code của nó khá nhàm chán. Thông thường chúng ta sẽ làm như sau: Khai báo cell cho table view
tableView.register(<#T##nib: UINib?##UINib?#>, forCellReuseIdentifier: <#T##String#>)

Reuse cell

tableView.dequeueReusableCell(withIdentifier: <#T##String#>, for: <#T##IndexPath#>)

Hôm nay mình sẽ dùng protocol để đỡ phải code lập đi lập lại hai khai báo trên.

Action

Tạo demo hiển thị list contact

Chúng ta sẽ tạo demo single page đơn giản với một table view chính. Đầu tiên, mình sẽ tạo class Contact với 2 dữ liệu cơ bản gồm name và image.

import UIKit
final class Contact: NSObject {
    var name: String!
    var avatar: UIImage?
    
    init(name: String, avatar: UIImage?) {
        self.name = name
        self.avatar = avatar
    }
}

Tiêp theo, mình sẽ tạo mock data cho contact với Mock protocol

protocol Mock  {
    static func Mock() -> Self
}

extension Contact : Mock {
    static func Mock() -> Contact {
        let arrayNames = ["Duong", "An", "Tuan", "Dat"]
        let arrayImages = [#imageLiteral(resourceName: "chandung"), #imageLiteral(resourceName: "chandung1"), #imageLiteral(resourceName: "chandung2"), #imageLiteral(resourceName: "chandung3")]
        let randomNameNum  = Int.randomInt(max: 3)
        let randomImageNum = Int.randomInt(max: 3)
        let contact = Contact(name: arrayNames[randomNameNum], avatar: arrayImages[randomImageNum])
        
        return contact
    }
}
extension Int {
    static func randomInt(max: Int) -> Int {
        return Int(arc4random_uniform(UInt32(max)))
    }
}

Tiếp theo là contact cell

class ContactTableViewCell: UITableViewCell, BaseCellProtocol {
    
    @IBOutlet weak var contactAvatarImageView: UIImageView!
    @IBOutlet weak var contactNameLabel: UILabel!
    
    var contact: Contact! {
        didSet {
            if let avatar = contact.avatar {
                self.contactAvatarImageView.image = avatar
            }
            self.contactNameLabel.text = contact.name
        }
    }
    override func awakeFromNib() {
        super.awakeFromNib()
    }
}

Cuối cùng là ContactTableView

class ContactsTableViewController: UITableViewController {
    var contactList = [Contact]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //Mock data
        for _ in 1...10  {
            self.contactList.append(Contact.Mock())
        }
    }
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return contactList.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        tableView.dequeueReusableCell(withIdentifier: "ContactTableViewCell", for: indexPath)
        cell.contact = contactList[indexPath.row]
        return cell
    }
}

Enhance việc khai báo Cell và sử dụng cell trong table

Chúng ta sẽ tạo protocol BaseTableCell với 2 hành vi chính, getNibName và getNib. Có thể dùng được cho cả collection view cell.

/**
 cell protocol for uicollectionview, uitableview
 */
protocol BaseCellProtocol {
    static func getNibName() -> String // return nibname of cell
    static func getNib() -> UINib // return nib of cell
}

/**
 default implement for cell
 */
extension BaseCellProtocol where Self : UIView {
    static func getNibName() -> String {
        return String(describing: self)
    }
    
    static func getNib() -> UINib {
        let mainBundle = Bundle.main
        return UINib.init(nibName: self.getNibName(), bundle: mainBundle)
    }
}

việc tạo extension sẽ giúp chúng ta đỡ công đi thao tác nhiều với String. Bạn chỉ cần đặt reuseIdentifier giống với tên cell là được.

Vậy là xong phần về TableViewCell, giờ chúng ta sẽ tiếp tục với TableView. Tiếp tục sử dụng protocol cho tableView. Chúng ta sẽ viết protocol cho 2 quá trình register cell vè deuse cell.

extension UITableView {
    func on_register< T : BaseCellProtocol>(type: T.Type)  {
        self.register(T.getNib(), forCellReuseIdentifier: T.getNibName())
        
    }
    
    func on_dequeue< T : BaseCellProtocol>(idxPath : IndexPath) -> T {
        guard let cell = self.dequeueReusableCell(withIdentifier: T.getNibName(), for: idxPath) as? T else {
            fatalError("couldnt dequeue cell with identifier \(T.getNibName())")
        }
        
        return cell
    }
}

Vậy giờ khi register cell cho tableView ta chỉ cần khai báo

 tableView.on_register(type: ContactTableViewCell.self)

Và khi dequeue cell cũng rất đơn giản.

let cell: ContactTableViewCell = self.tableView.on_dequeue(idxPath: indexPath)

Kết

Vậy là xong, chúng ta có thể làm gọn việc khai báo và sử dụng cell trong tableView đỡ phức tạp đi 1 tý. Có thể áp dụng cách trên với collection view. Các bạn có thể down demo tại đây Bài viết có nguồn từ https://kipalog.com/posts/Slim-tableview