Tabel Generik Statis

gambar

Kita semua sering harus berurusan dengan tabel statis, mereka dapat menjadi pengaturan aplikasi kita, layar otorisasi, layar "tentang kita", dan banyak lainnya. Namun seringkali, pengembang pemula tidak menerapkan pola pengembangan untuk tabel tersebut dan menulis semua dalam satu kelas sistem yang tidak dapat diskalakan dan tidak fleksibel.

Tentang bagaimana saya mengatasi masalah ini - di bawah potongan.

Apa yang kamu bicarakan


Sebelum Anda memecahkan masalah tabel statis, Anda harus memahami apa itu tabel. Tabel statis adalah tabel di mana Anda sudah tahu jumlah baris dan konten yang ada di dalamnya. Contoh tabel serupa di bawah ini.

gambar

Masalah


Untuk mulai dengan, ada baiknya mengidentifikasi masalah: mengapa kita tidak bisa hanya membuat ViewController yang akan menjadi UITableViewDelegate dan UITableViewDatasource dan cukup jelaskan semua sel yang Anda butuhkan? Setidaknya - ada 5 masalah dengan tabel kami:

  1. Sulit untuk diukur
  2. Tergantung indeks
  3. Tidak fleksibel
  4. Kurangnya penggunaan kembali
  5. Membutuhkan banyak kode untuk diinisialisasi

Solusi


Metode untuk memecahkan masalah didasarkan pada fondasi berikut:

  1. Penghapusan tanggung jawab konfigurasi tabel di kelas yang terpisah ( Konstruktor )
  2. Wrapper khusus di atas UITableViewDelegate dan UITableViewDataSource
  3. Menghubungkan sel ke protokol khusus untuk digunakan kembali
  4. Membuat model data Anda sendiri untuk setiap tabel

Pertama saya ingin menunjukkan bagaimana ini digunakan dalam praktek - maka saya akan menunjukkan bagaimana semuanya diterapkan di bawah tenda.

Implementasi


Tugasnya adalah membuat tabel dengan dua sel teks dan satu kosong di antaranya.

Pertama-tama, saya membuat TextTableViewCell biasa dengan UILabel .
Selanjutnya, setiap UIViewController dengan tabel statis memerlukan Konstruktornya sendiri, mari kita buat:

class ViewControllerConstructor: StaticConstructorContainer { typealias ModelType = <#type#> } 

Ketika kita mewarisinya dari StaticConstructorContainer , pertama-tama, protokol Generic mengharuskan kita untuk mengetik model ( ModelType ) - ini adalah jenis model sel yang juga perlu kita buat, mari kita lakukan.

Saya menggunakan enum untuk ini, karena lebih cocok untuk tugas kami dan di sini kesenangan dimulai. Kami akan mengisi tabel kami dengan konten menggunakan protokol seperti: Judul, Subtitle, Berwarna, Berwarna dan sebagainya. Seperti yang bisa Anda tebak, protokol-protokol ini bertanggung jawab untuk menampilkan teks. Katakanlah protokol Titled memerlukan judul: String? , dan jika sel kami mendukung tampilan judul , itu akan mengisinya. Mari kita lihat seperti apa:

 protocol Fonted { var font: UIFont? { get } } protocol FontedConfigurable { func configure(by model: Fonted) } protocol Titled { var title: String? { get } } protocol TitledConfigurable { func configure(by model: Titled) } protocol Subtitled { var subtitle: String? { get } } protocol SubtitledConfigurable { func configure(by model: Subtitled) } protocol Imaged { var image: UIImage? { get } } protocol ImagedConfigurable { func configure(by model: Imaged) } 

Oleh karena itu, hanya sebagian kecil dari protokol tersebut yang disajikan di sini, Anda dapat membuatnya sendiri, seperti yang Anda lihat - sangat sederhana. Saya mengingatkan Anda bahwa kami membuat mereka 1 kali untuk 1 tujuan dan kemudian melupakan mereka dan dengan tenang menggunakannya.

Sel kita ( dengan teks ) pada dasarnya mendukung hal-hal berikut: Font teks, teks itu sendiri, warna teks, warna latar belakang sel, dan umumnya segala hal yang terlintas dalam pikiran.

Kami hanya membutuhkan gelar sejauh ini. Oleh karena itu, kami mewarisi model kami dari Titled. Di dalam model dalam kasus, kami menunjukkan jenis sel apa yang akan kita miliki.

 enum CellModel: Titled { case firstText case emptyMiddle case secondText var title: String? { switch self { case .firstText: return " - " case .secondText: return " - " default: return nil } } } 

Karena tidak ada label di tengah (sel kosong), Anda dapat mengembalikan nol.
Kami menyelesaikan sel C dan Anda dapat memasukkannya ke konstruktor kami.

 class ViewControllerConstructor: StaticConstructorContainer { typealias ModelType = CellModel var models: [CellModel] //        ,    func cellType(for model: CellModel) -> Self.StaticTableViewCellClass.Type { //      ,    } func configure(cell: UITableViewCell, by model: CellModel) { //      ,   ,      } func itemSelected(item: CellModel) { //  didSelect,     } } 

Dan sebenarnya, ini semua kode kita. Kita dapat mengatakan bahwa meja kita sudah siap. Mari kita mengisi data dan melihat apa yang terjadi.

Oh ya, aku hampir lupa. Kita perlu mewarisi sel kita dari protokol TitledConfigurable sehingga bisa memasukkan judul ke dirinya sendiri. Sel juga mendukung ketinggian dinamis.

 extension TextTableViewCell: TitledConfigurable { func configure(by model: Titled) { label.text = model.title } } 

Seperti apa konstruktor yang diisi:

 class ViewControllerConstructor: StaticConstructorContainer { typealias ModelType = CellModel var models: [CellModel] = [.firstText, .emptyMiddle, .secondText] func cellType(for model: CellModel) -> StaticTableViewCellClass.Type { switch model { case .emptyMiddle: return EmptyTableViewCell.self case .firstText, .secondText: return TextTableViewCell.self } } func configure(cell: UITableViewCell, by model: CellModel) { cell.selectionStyle = .none } func itemSelected(item: CellModel) { switch item { case .emptyMiddle: print("  ") default: print("  ...") } } } 

Terlihat sangat kompak, bukan?

Sebenarnya, hal terakhir yang harus kita lakukan adalah menghubungkan semuanya ke ViewController'e:

 class ViewController: UIViewController { private let tableView: UITableView = { let tableView = UITableView() return tableView }() private let constructor = ViewControllerConstructor() private lazy var delegateDataSource = constructor.delegateDataSource() override func viewDidLoad() { super.viewDidLoad() constructor.setup(at: tableView, dataSource: delegateDataSource) } } 

Semuanya siap, kita harus menjadikan delegateDataSource sebagai properti terpisah di kelas kita sehingga tautan yang lemah tidak merusak fungsi apa pun.

Kita dapat berlari dan menguji:

gambar

Seperti yang Anda lihat, semuanya berfungsi.

Sekarang mari kita simpulkan dan pahami apa yang telah kita capai:

  1. Jika kami membuat sel baru dan ingin mengganti yang sekarang dengan itu, maka kami melakukan ini dengan mengubah satu variabel. Kami memiliki sistem tabel yang sangat fleksibel
  2. Kami menggunakan kembali semua sel. Semakin banyak sel yang Anda tautkan ke tabel ini, semakin mudah dan mudah untuk bekerja dengannya. Bagus untuk proyek besar.
  3. Kami telah mengurangi jumlah kode untuk membuat tabel. Dan kita harus menulis lebih sedikit ketika kita memiliki banyak protokol dan sel statis di proyek.
  4. Kami membawa konstruksi tabel statis dari UIViewController ke Konstruktor
  5. Kami telah berhenti bergantung pada indeks, kami dapat dengan aman menukar sel dalam array dan logika tidak akan pecah.

Kode untuk proyek uji di akhir artikel.

Bagaimana cara kerjanya dari dalam ke luar?


Cara kerja protokol sudah kita bahas. Sekarang kita perlu memahami bagaimana seluruh konstruktor dan kelas terkait bekerja.

Mari kita mulai dengan konstruktor itu sendiri:
 protocol StaticConstructorContainer { associatedtype ModelType var models: [ModelType] { get } func cellType(for model: ModelType) -> StaticTableViewCellClass.Type func configure(cell: UITableViewCell, by model: ModelType) func itemSelected(item: ModelType) } 

Ini adalah protokol umum yang membutuhkan fungsi yang sudah akrab bagi kita.

Lebih menarik adalah ekstensi :

 extension StaticConstructorContainer { typealias StaticTableViewCellClass = StaticCell & NibLoadable func delegateDataSource() -> StaticDataSourceDelegate<Self> { return StaticDataSourceDelegate<Self>.init(container: self) } func setup<T: StaticConstructorContainer>(at tableView: UITableView, dataSource: StaticDataSourceDelegate<T>) { models.forEach { (model) in let type = cellType(for: model) tableView.register(type.nib, forCellReuseIdentifier: type.name) } tableView.delegate = dataSource tableView.dataSource = dataSource dataSource.tableView = tableView } } 

Fungsi pengaturan yang kami panggil di ViewController kami mendaftarkan semua sel untuk kami dan mendelegasikan dataSource dan mendelegasikan .

Dan delegateDataSource () menciptakan bagi kami UITableViewDataSource wrapper dan UITableViewDelegate . Mari kita melihatnya:

 class StaticDataSourceDelegate<Container: StaticConstructorContainer>: NSObject, UITableViewDelegate, UITableViewDataSource { private let container: Container weak var tableView: UITableView? init(container: Container) { self.container = container } func reload() { tableView?.reloadData() } func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { let type = container.cellType(for: container.models[indexPath.row]) return type.estimatedHeight ?? type.height } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let type = container.cellType(for: container.models[indexPath.row]) return type.height } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return container.models.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let model = container.models[indexPath.row] let type = container.cellType(for: model) let cell = tableView.dequeueReusableCell(withIdentifier: type.name, for: indexPath) if let typedCell = cell as? TitledConfigurable, let titled = model as? Titled { typedCell.configure(by: titled) } if let typedCell = cell as? SubtitledConfigurable, let subtitle = model as? Subtitled { typedCell.configure(by: subtitle) } if let typedCell = cell as? ImagedConfigurable, let imaged = model as? Imaged { typedCell.configure(by: imaged) } container.configure(cell: cell, by: model) return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let model = container.models[indexPath.row] container.itemSelected(item: model) } } 

Saya pikir tidak ada pertanyaan tentang fungsi heightForRowAt , numberOfRowsInSection , didSelectRowAt , mereka hanya mengimplementasikan fungsionalitas yang jelas. Metode yang paling menarik di sini adalah cellForRowAt .

Di dalamnya, kami tidak menerapkan logika yang paling indah. Kami dipaksa untuk menulis setiap protokol baru ke sel di sini, tetapi kami melakukannya sekali - jadi tidak begitu menakutkan. Jika model sesuai dengan protokol, sama seperti sel kita, maka kita akan mengkonfigurasinya. Jika ada yang punya ide tentang cara mengotomatisasi ini, saya akan senang mendengarkan komentar.

Ini mengakhiri logika. Saya tidak menyentuh kelas utilitarian pihak ketiga dalam sistem ini, Anda dapat membaca kode lengkapnya di sini .

Terima kasih atas perhatian anda!

Source: https://habr.com/ru/post/id439016/


All Articles