Every time I write a TableView it’s a love-hate relationship, the code always feels very similar, but the details are different and I can’t learn from it. The reason is that the code is not really standardized and modular. After reviewing a few articles, I’ve come up with a paradigm that goes a long way toward normalizing TableView writing. This article is not only a summary of TableView, but also the use of protocol, enumeration and other types of discussion.
References are as follows:
- Introduction to the protocol – oriented MVVM architecture
- Dealing with Complex Table Views in iOS and Keeping Your Sanity
- Handle complex Table Views in iOS with elegance
This paper focused on optimization from the perspective of data model, and the data model of TableView was divided into three cases: Dynamic type Cells (Dynamic Prototypes), Static type Cells (Static Cells), and Dynamic and Static mixed types.
First, take a look at the overall structure diagram of the optimized model:
The key to optimization lies in the proper design of the model by matching protocols and enumerations. Let’s look at dynamic typing first:
Dynamic type TableView optimization
We’ll continue with the Github sample project.
In the last example project we had a simple list showing the book list. Now we will add the following functionality:
1. Get my books from Douban
2. The list is divided into three Sectinon displays: books you want to read, books you are reading and books you have read
3. Alternate the two types of cells (i.e., heterogeneous type data model) in the list: book name and book score
4. The detail page of book scoring will contain mixed data of dynamic and static.
The final effect is as follows:
Now start coding: functions 1 and 2
- Function 1 requires us to make a network request and parse the returned data into the specified model. Here we use
URLSession
To send the request, we first add two protocols:
๐NetworkProtocol. Swift: We use generic constraints in the Send method, which is more efficient than using the Request protocol as the parameter type directly.
/// Network request sending protocol
protocol Client {
var host: String { get }
func send<T: Request>(_ r: T, handler: @escaping (Data?) -> Void)}/// network request content protocol
protocol Request {
var path: String { get }
var method: HTTPMethod { get }
var parameter: [String: Any] { get}}Copy the code
Add two enumerated types:
๐ Enums. Swift:
/// Http request method
public enum HTTPMethod: String {
case GET
case POST
}
/// Host type
///
/// -doubanAPI: doubanAPI
enum HostType: String {
case doubanAPI = "https://api.douban.com"
}
Copy the code
Add the request model of the corresponding protocol:
๐URLSessionClient. Swift: We don’t parse the returned Data here, we leave it to the Data model.
/// Network client model
struct URLSessionClient: Client {
let host: String
func send<T: Request>(_ requestInfo: T, handler: @escaping (Data?) -> Void) {
let url = URL(string: host.appending(requestInfo.path))!
var request = URLRequest(url: url)
request.httpMethod = requestInfo.method.rawValue
let task = URLSession.shared.dataTask(with: request) {
data, _, error in
if let data = data {
DispatchQueue.main.async { handler(data) }
} else {
DispatchQueue.main.async { handler(nil) }
}
}
task.resume()
}
}
Copy the code
๐ BookRequest. Swift: Here we are parsing the requested Data type through Swift 4.0’s JSONDecoder, which is much more elegant than previous JSON parsing methods, so our Data models will now support the Codable protocol to take advantage of this convenience. The BookCollections data model in the code will be created later.
/// network request model for book query
struct BookRequest: Request {
let userName: String / / user name
let status: String // Reading status: wish to read: reading: reading read: read
let start: Int // Start number
let count: Int // Query the maximum number at a time
var path: String {
return "/v2/book/user/\(userName)/collections? status=\(status)&start=\(start)&count=\ [count)"
}
let method: HTTPMethod=.GET
let parameter: [String: Any] = [:]
func send(handler: @escaping (BookCollections?) -> Void) {
URLSessionClient(host: HostType.doubanAPI.rawValue).send(self) { data in
guard let data = data else { return }
if let bookCollections = try? JSONDecoder().decode(BookCollections.self, from: data) {
handler(bookCollections)
} else {
handler(nil)
print("JSON parse failed")}}}}Copy the code
- Now let’s create the parsed data model. The following four data models are consistent with the Json data structure returned by Douban API (only the required attributes are retained), so just copy it as is. Note that these structures are gradually nested downward, consistent with the nested structure of API, which is
Codable
The best part of the deal.
๐ DataModel. Swift:
typealias DataModel = BookCollections
struct BookCollections: Codable {
var total: Int = 0
var collections: [MyBook] = []}struct MyBook: Codable {
var status: String = ""
var book: Book
}
struct Book: Codable {
var title: String = ""
var subtitle: String = ""
var author: [String] = []
var publisher: String = ""
var isbn13: String?
var price: String = ""
var pubdate: String = ""
var summary: String = ""
var rating: BookRating
var id: String = ""
}
struct BookRating: Codable {
var average: String = ""
var numRaters: Int = 0
}
Copy the code
The above models can also be called API models, which need to be transformed when applied to the table. In order to keep decoupling as far as possible, we try to keep separation of functions when designing the model here. We then design the data model to correspond to the requirements of the table, but in order to achieve this correspondence, we also need a transformation layer, where we use the ViewModel to take responsibility for the model transformation.
- Let’s start with the protocol definition. This set of protocols defines the main data requirements of the table, namely the data displayed by all cells and the information displayed by sections.
๐ TableDataModelProtocol. Swift:
/// TableView dynamic type data model protocol.
// The first layer corresponds to sections, and the second layer corresponds to cells under sections.
/// and the Section data array
protocol TableViewDynamicCellDataModel {
associatedtype TableDataModelType: CellModelType
associatedtype SectionItem: TableViewSection
var dynamicTableDataModel: [[TableDataModelType]] { get set }
var sectionsDataModel: [SectionItem] { get set}}/// TableView section information structure model protocol, including section title information, etc.
protocol TableViewSection {
var headerTitle: String? { get }
var footerTitle: String? { get}}/// Cell Unified Data Model protocol
protocol CellModelType {
// This is a wrapper protocol, easy to use in other protocols, can be empty.
}
Copy the code
๐ CellDataModelProtocol. Swift:
/// Cell data protocol in the book list
protocol BookInfoCellProtocol {
var identifier: CellIdentifierType { get }
var title: String { get }
var authors: String { get }
var publisher: String { get }
var isbn13: String { get }
var price: String { get }
var pubdate: String { get }
var summary: String { get}}Copy the code
To make the Cell support multiple data types, we use the enumeration BookListCellModelType to change the heterogeneous type into the homogeneous type.
For turning heterogeneity into isomorphism, you can use protocols in addition to enumerations to conform multiple types to the same protocol, but using code to distinguish between different types is still not elegant. The advantage of enumerations is that you can take advantage of compiler checks when using switch statements, but the biggest disadvantage of enumerations is that extracting values can be a bit cumbersome, as you’ll see later.
The least recommended, however, is to use Any and AnyObject.
๐ enums. swift: CellModelType is the wrapper protocol, the protocol itself is empty, just to cover all Cell data type enumerations.
/// The data model type used by the book list Cell
///
/// -bookinfo: basic book information
enum BookListCellModelType: CellModelType {
case bookInfo(BookInfoCellModel)
// It will be easy to extend multiple data models later
}
Copy the code
- Next, we complete the data model required by the protocol:
๐ cellmodel. swift: Since cells in the book list are reusable, we need to indicate identifiers as enumerations.
/// The book name of the book list is the data structure of the cell
struct BookInfoCellModel: BookInfoCellProtocol {
var identifier = CellIdentifierType.bookInfoCell
var title: String = ""
var authors: String = ""
var publisher: String = ""
var isbn13: String = ""
var price: String = ""
var pubdate: String = ""
var summary: String = ""
}
/// Lists all dynamic cell identifiers contained in the table
public enum CellIdentifierType: String {
case bookInfoCell
}
Copy the code
๐ sectionModel. swift: cellType and cellCount are reserved for static tables.
/// Section data structure in TableView
struct SectionModel: TableViewSection {
var headerTitle: String?
var footerTitle: String?
var cellType: CellType
var cellCount: Int
init(headerTitle: String? , footerTitle:String? , cellType:CellType = .dynamicCell,
cellCount: Int = 0)
{
self.headerTitle = headerTitle
self.footerTitle = footerTitle
self.cellType = cellType
self.cellCount = cellCount
}
}
/ / / cell types
///
/// -staticcell: static
/// - dynamicCell: dynamic
public enum CellType: String {
case staticCell
case dynamicCell
}
Copy the code
- With the data model in place, the next step is to implement the VM, and the main thing the VM needs to do is transform the API model -> table data model.
๐ TableViewViewModel. Swift: DataModel is the API parsed model. [[BookListCellModelType]] is the DataModel required by the table. The two-dimensional array is used because it is very straightforward to use in the DataSource proxy method.
The getTableDataModel method is used to wrap data structures. The getBookInfo method does the actual conversion of the data details, if there is a change in the field mapping that can be modified here.
struct TableViewViewModel {
/// create a unified data model for tables
///
/// -parameter model: raw data model
/// - Returns: table data model
static func getTableDataModel(model: DataModel)- > [[BookListCellModelType]] {
var bookWishToRead: [BookListCellModelType] = []
var bookReading: [BookListCellModelType] = []
var bookRead: [BookListCellModelType] = []
for myBook in model.collections {
guard let status = BookReadingStatus(rawValue: myBook.status) else {
return[]}let bookInfo = getBookInfo(model: myBook.book)
switch status {
case .wish:
bookWishToRead.append(bookInfo)
case .reading:
bookReading.append(bookInfo)
case .read:
bookRead.append(bookInfo)
case .all:
break}}return [bookWishToRead, bookReading, bookRead]
}
// get the BookInfoCellModel data model
///
/// -parameter model: raw data submodel
/// - Returns: unified cell data model
static func getBookInfo(model: Book) -> BookListCellModelType {
var cellModel = BookInfoCellModel()
cellModel.title = model.title
cellModel.authors = model.author.reduce("", {$0= ="" ? $1 : $0 + "ใ" + $1 })
cellModel.publisher = model.publisher
cellModel.isbn13 = model.isbn13 ?? ""
cellModel.price = model.price
cellModel.pubdate = model.pubdate
cellModel.summary = model.summary
return BookListCellModelType.bookInfo(cellModel)
}
}
/// read the book
public enum BookReadingStatus: String {
case wish
case reading
case read
case all = ""
}
Copy the code
- Ok, with the above model, we can call the API to get the data.
๐ MainTableViewController. Swift: loadData () method after loading the data will automatically refresh the form
/// Data source object
var dynamicTableDataModel: [[BookListCellModelType]] = [] {
didSet {
tableView.reloadData()
}
}
override func viewDidLoad(a) {
super.viewDidLoad()
loadData() // Load data
// ...
}
/// load initial data
func loadData(a) {
let request = BookRequest(userName: "pmtao", status: "", start: 0.count: 40)
request.send { data in
guard let dataModel = data else { return }
let tableDataModel = TableViewViewModel.getTableDataModel(model: dataModel)
self.dynamicTableDataModel = tableDataModel
}
}
Copy the code
- It’s easy to write the datasource proxy method now:
๐MainTableViewController+DataSource. Swift: The following code can be almost painlessly ported to another TabelView, just change the name in the code comment. DataSource DataSource DataSource DataSource DataSource DataSource DataSource DataSource DataSource DataSource There is a configureCell method, which we implement in the custom Cell class file and let the Cell do its configuration, feeding the Cell data without converting, thanks to the BookInfoCellProtocol.
override func numberOfSections(in tableView: UITableView) -> Int {
return dynamicTableDataModel.count
}
override func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return dynamicTableDataModel[section].count
}
override func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let section = indexPath.section
let row = indexPath.row
let model = dynamicTableDataModel[section][row]
switch model {
case let .bookInfo(bookInfo): // The bookInfo port is renamed
let identifier = bookInfo.identifier.rawValue // The bookInfo port is renamed
let cell = tableView.dequeueReusableCell(
withIdentifier: identifier,
for: indexPath) as! BookInfoCell // BookInfoCell transplant the name
cell.configureCell(model: bookInfo) // The bookInfo port is renamed
return cell
}
}
Copy the code
Next, implement function 3
- Show two types of Cell (i.e. heterogeneous type data model) : book name, book score, and extend the previous model:
๐ enums. swift: Adds the Cell data type for scoring information.
/// The data model type used by the book list Cell
///
/// -bookinfo: basic book information
/// -bookrating: indicates the bookRating information
enum BookListCellModelType: CellModelType {
case bookInfo(BookInfoCellModel)
case bookRating(BookRatingCellModel)}Copy the code
๐ CellDataModelProtocol. Swift: increase the Cell data protocol rating information.
/// Cell data protocol in the book list
protocol BookRatingCellProtocol {
var identifier: CellIdentifierType { get }
var average: String { get }
var numRaters: String { get }
var id: String { get }
var title: String { get}}Copy the code
๐ cellmodel. swift: Add the data structure of the rating classification cell
/// The data structure of the book review classification cell for the book list
struct BookRatingCellModel: BookRatingCellProtocol {
var identifier = CellIdentifierType.bookRatingCell
var average: String = ""
var numRaters: String = ""
var id: String = ""
var title: String = ""
}
Copy the code
๐ tableViewViewModel. swift: add data model conversion method to VM:
static func getTableDataModel(model: DataModel)- > [[BookListCellModelType]] {...let bookRating = getBookRating(model: myBook.book)
switch status {
case .wish:
bookWishToRead.append(bookInfo)
bookWishToRead.append(bookRating) // Add the data type
case .reading:
bookReading.append(bookInfo)
bookReading.append(bookRating) // Add the data type
case .read:
bookRead.append(bookInfo)
bookRead.append(bookRating) // Add the data type
case .all:
break}... }// get BookRatingCellModel data model
///
/// -parameter model: raw data submodel
/// - Returns: unified cell data model
static func getBookRating(model: Book) -> BookListCellModelType {
var cellModel = BookRatingCellModel()
cellModel.average = "Grading:" + model.rating.average
cellModel.numRaters = "Number of evaluators:" + String(model.rating.numRaters)
cellModel.id = model.id
cellModel.title = model.title
return BookListCellModelType.bookRating(cellModel)
}
Copy the code
For the final step, DataSource tweaks:
๐MainTableViewController+DataSource. Swift: Add a case and the compiler will automatically prompt you to use enumerations to encapsulate heterogeneous data types ๐.
override func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let section = indexPath.section
let row = indexPath.row
let model = dynamicTableDataModel[section][row]
switch model {
case let .bookInfo(bookInfo):
let identifier = bookInfo.identifier.rawValue
let cell = tableView.dequeueReusableCell(
withIdentifier: identifier, for: indexPath) as! BookInfoCell
cell.configureCell(model: bookInfo)
return cell
// Add data type section:
case let .bookRating(bookRating):
let identifier = bookRating.identifier.rawValue
let cell = tableView.dequeueReusableCell(
withIdentifier: identifier, for: indexPath) as! BookRatingCell
cell.configureCell(model: bookRating)
return cell
}
}
Copy the code
Static type TableView optimization
Let’s revamp the book details page
The detail page is a purely statically typed table that encapsulates protocols and models in a similar way, and is extremely succinct.
๐ CellDataModelProtocol. Swift: increase the Cell data model agreement page for details
/// Book details Cell data protocol
protocol BookDetailCellProtocol {
var title: String { get }
var authors: String { get }
var publisher: String { get}}Copy the code
๐ cellModel. swift: Add data model to detail page Cell
/// The data structure of the book details class cell
struct BookDetailCellModel: BookDetailCellProtocol {
var title: String = ""
var authors: String = ""
var publisher: String = ""
}
Copy the code
๐ tableviewviewModel. swift: Model transformation to add book details
// get the BookDetailCellModel data model
///
/// -parameter Model: BookInfoCellModel
/// - Returns: BookDetailCellModel data model
static func getBookDetail(model: BookInfoCellModel) -> BookDetailCellModel {
var cellModel = BookDetailCellModel()
cellModel.title = model.title
cellModel.authors = model.authors
cellModel.publisher = model.publisher
return cellModel
}
Copy the code
๐ TableDataModelProtocol. Swift: increase the static type TableView data model agreement
/// TableView static type data model protocol.
/// contains Cell structure data, Section data array
protocol TableViewStaticCellDataModel {
associatedtype StaticCellDataModel
associatedtype SectionItem: TableViewSection
var staticTableDataModel: StaticCellDataModel { get set }
var sectionsDataModel: [SectionItem] { get set}}Copy the code
๐ DetailTableViewController. Swift: the final integration, a total of 40, is a very concise ๐.
import UIKit
class DetailTableViewController: UITableViewController.TableViewStaticCellDataModel {
// MARK: 1.--@IBOutlet attribute definition -----------๐
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var authorsLabel: UILabel!
@IBOutlet weak var publisherLabel: UILabel!
// MARK: 2.-- Instance attribute definitions ----------------๐
var staticTableDataModel = BookDetailCellModel(a)var sectionsDataModel: [SectionModel] = []
MARK: 3.-- View lifecycle ----------------๐
override func viewDidLoad(a) {
super.viewDidLoad()
setSectionDataModel() // Set the section data model
configureCell(model: self.staticTableDataModel) // Configure the Cell to display content
}
override func didReceiveMemoryWarning(a) {
super.didReceiveMemoryWarning()
}
// MARK: 4.-- Handle main logic -----------------๐
/// Set the section data model
func setSectionDataModel(a) {
sectionsDataModel = [SectionModel(headerTitle: nil, footerTitle: nil, cellCount: 3)]}/// Configure static Cell to display content
func configureCell<T: BookDetailCellProtocol>(model: T){ nameLabel? .text = model.title authorsLabel? .text = model.authors publisherLabel? .text = model.publisher }// MARK: 5.-- Data source method ------------------๐
override func numberOfSections(in tableView: UITableView) -> Int {
return sectionsDataModel.count
}
override func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return sectionsDataModel[section].cellCount
}
}
Copy the code
Dynamic and static hybrid type TableView optimization
The last bone: Function 4
Function 4 To implement a static and dynamic hybrid table, this topic is also a very common topic in the TabelView transformation. I tried several solutions, always trying to get lazy with a bit of hacking technology, but after the experiment I found that I had to go with the conventional thinking. The summary is as follows:
- Firstly, static tables are used for design, and only one Cell is reserved for dynamic data (pure placeholder, without any setting).
- new
UITableViewCell
Subclasses and associatedxib
File, addIdentifier
This is used to reuse the dynamic cell part. - Determine the cell type (static, dynamic) in the data source and return the corresponding cell.
Start now.
๐ enums. swift: Adds point types
/// Lists all dynamic cell identifiers contained in the table
public enum CellIdentifierType: String {
case bookInfoCell
case bookRatingCell
case bookReviewTitleCell
}
/// Cell NIb file type
public enum CellNibType: String {
case BookReviewListTableViewCell
}
/// The type of data model used by the scoring item Cell on the book review list page
enum BookReviewCellModelType: CellModelType {
case bookReviewList(BookReviewListCellModel)}Copy the code
๐ TableDataModelProtocol. Swift: create a hybrid version of the data model agreement, in fact, the combined dynamic and static data types
/// TableView data model protocol for dynamic and static mixed types.
/// contains dynamic Cell 2d array model, static Cell structure data, Section data array, dynamic Cell reuse information.
protocol TableViewMixedCellDataModel {
associatedtype TableDataModelType: CellModelType
associatedtype StaticCellDataModel
associatedtype SectionItem: TableViewSection
var dynamicTableDataModel: [[TableDataModelType]] { get set }
var staticTableDataModel: StaticCellDataModel { get set }
var sectionsDataModel: [SectionItem] { get set }
var cellNibs: [(CellNibType.CellIdentifierType)] { get set}}Copy the code
๐ CellDataModelProtocol. Swift: a Cell is divided into two parts of the dynamic and static data model agreement.
/// Book review summary list data protocol
protocol BookReviewListCellProtocol {
var identifier: CellIdentifierType { get }
var title: String { get }
var rate: String { get }
var link: String { get}}/// Book review title data protocol
protocol BookReviewHeadCellProtocol {
var title: String { get }
var rate: String { get}}Copy the code
๐BookReviewRequest. Swift: Comment data requires a separate network request. Add a request model.
struct BookReviewRequest: Request {
let bookID: String ID / / books
let start: Int // Start number
let count: Int // Query the maximum number at a time
var path: String {
return "/v2/book/\(bookID)/reviews? start=\(start)&count=\ [count)"
}
let method: HTTPMethod=.GET
let parameter: [String: Any] = [:]
func send(handler: @escaping (BookReview?) -> Void) {
URLSessionClient(host: HostType.doubanAPI.rawValue).send(self) { data in
guard let data = data else { return }
if let bookReviews = try? JSONDecoder().decode(BookReview.self, from: data) {
handler(bookReviews)
} else {
handler(nil)
print("JSON parse failed")}}}}Copy the code
๐ datamodel. swift: THE API model has also been added.
struct BookReview: Codable {
var reviews: [Review] = []
struct Review: Codable {
var rating: Score
var title: String = ""
var alt: String = "" // Comments page link
}
struct Score: Codable {
var value: String = ""}}Copy the code
๐ cellmodel. swift: Models required to implement the cell data protocol, also separated by dynamic and static.
/// The summary list of book review details is a cell data structure
struct BookReviewListCellModel: BookReviewListCellProtocol {
var identifier = CellIdentifierType.bookReviewTitleCell
var title: String = ""
var rate: String = ""
var link: String = ""
}
/// The data structure of the comment title class cell for book review details
struct BookReviewHeadCellModel: BookReviewHeadCellProtocol {
var id: String = ""
var title: String = ""
var rate: String = ""
}
Copy the code
๐ tableViewViewModel. swift: Convert API model to cell data model in VM.
// get the BookReviewListCellModel data model
///
/// -parameter model: BookReview data model
/// - Returns: the comment list model required by the book review page
static func getBookReviewList(model: BookReview)- > [[BookReviewCellModelType]] {
var cellModel: [BookReviewCellModelType] = []
for review in model.reviews {
var bookReviewListCellModel = BookReviewListCellModel()
bookReviewListCellModel.title = review.title
bookReviewListCellModel.rate = "Grading:" + review.rating.value
bookReviewListCellModel.link = review.alt
// Convert to enum type
let model = BookReviewCellModelType.bookReviewList(bookReviewListCellModel)
cellModel.append(model)
}
return [[], cellModel]
}
// get the BookReviewHeadCellModel data model
///
/// -parameter model: Book data model
/// - Returns: Title information required for the book review page
static func getBookReviewHead(model: BookRatingCellModel) -> BookReviewHeadCellModel {
var cellModel = BookReviewHeadCellModel()
cellModel.id = model.id
cellModel.title = model.title
cellModel.rate = model.average
return cellModel
}
Copy the code
๐ ReviewTableViewController. Swift: the final integration, the static parts of the Cell directly to set up the IBOutlet controls established, using the data model mapping is good.
class ReviewTableViewController: UITableViewController.TableViewMixedCellDataModel {
// MARK: 1.--@IBOutlet attribute definition -----------๐
@IBOutlet weak var bookNameLabel: UILabel!
@IBOutlet weak var rateLabel: UILabel!
// MARK: 2.-- Instance attribute definitions ----------------๐
/// Data source object
var dynamicTableDataModel: [[BookReviewCellModelType]] = [] {
didSet {
if shouldReloadTable {
setSectionDataModel()
tableView.reloadData()
}
}
}
var staticTableDataModel = BookReviewHeadCellModel(a)var sectionsDataModel: [SectionModel] = []
var cellNibs: [(CellNibType.CellIdentifierType= [()].BookReviewListTableViewCell, .bookReviewTitleCell)]
/// whether the table can be refreshed when data is updated
var shouldReloadTable: Bool = false
Copy the code
A few more initialization Settings, note that static cells can already be configured via IBOutlet in the viewDidLoad method.
MARK: 3.-- View lifecycle ----------------๐
override func viewDidLoad(a) {
super.viewDidLoad()
loadData() // Load data
setSectionDataModel() // Set the section data model
configureStaticCell(model: staticTableDataModel) // Configure the Cell to display content
setupView() // View initialization
}
// MARK: 4.-- Handle main logic -----------------๐
/// load initial data
func loadData(a) {
let request = BookReviewRequest(bookID: staticTableDataModel.id,
start: 0.count: 3)
request.send { data in
guard let dataModel = data else { return }
let tableDataModel = TableViewViewModel.getBookReviewList(model: dataModel)
self.shouldReloadTable = true
self.dynamicTableDataModel = tableDataModel
}
}
/// Set the section data model
func setSectionDataModel(a) {
let section1 = SectionModel(
headerTitle: "Books",
footerTitle: nil,
cellType: .staticCell,
cellCount: 2)
var section2CellCount = 0
if dynamicTableDataModel.count > 0 {
section2CellCount = dynamicTableDataModel[1].count
}
let section2 = SectionModel(
headerTitle: "Selected Reviews",
footerTitle: nil,
cellType: .dynamicCell,
cellCount: section2CellCount)
sectionsDataModel = [section1, section2]
}
/// Configure static Cell to display content
func configureStaticCell<T: BookReviewHeadCellProtocol>(model: T){ bookNameLabel? .text = model.title rateLabel? .text = model.rate }// view initializes related Settings
func setupView(a) {
// Register the cell NIb file
for (nib, identifier) in cellNibs {
let nib = UINib(nibName: nib.rawValue, bundle: nil)
tableView.register(nib, forCellReuseIdentifier: identifier.rawValue)
}
}
Copy the code
Here comes the key data source method:
// MARK: 8.-- Data source method ------------------๐
override func numberOfSections(in tableView: UITableView) -> Int {
return sectionsDataModel.count
}
override func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return sectionsDataModel[section].cellCount
}
Copy the code
The sectionsDataModel data already contains the dynamic and static type of the Cell, so it can be directly used to determine. Statically typed cells can be retrieved directly from the super property, which is the controller object itself. The retrieved cells are instantiated from the StoryBoard, so that cellForRowAt does not call its own methods in a loop. Dynamically typed cells simply call the dequeueReusableCell method, noting the one with the for: indexPath argument.
override func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath)
-> UITableViewCell
{
let section = indexPath.section
let row = indexPath.row
if sectionsDataModel[section].cellType == .staticCell {
let cell = super.tableView(tableView, cellForRowAt: indexPath)
return cell
} else {
let model = dynamicTableDataModel[section][row]
switch model {
case let .bookReviewList(bookReviewList):
let identifier = bookReviewList.identifier.rawValue
let cell = tableView.dequeueReusableCell(
withIdentifier: identifier, for: indexPath) as! BookReviewListTableViewCell
cell.configureCell(model: bookReviewList)
return cell
}
}
}
Copy the code
The view-proxy methods are a side effect of using static TableViews to implement dynamic cell effects.
// MARK: 9.-- View proxy method ----------------๐
// Use this proxy method when reusing static cells
override func tableView(_ tableView: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat
{
let section = indexPath.section
if sectionsDataModel[section].cellType == .staticCell {
return super.tableView(tableView, heightForRowAt: indexPath)
} else {
let prototypeCellIndexPath = IndexPath(row: 0, section: indexPath.section)
return super.tableView(tableView, heightForRowAt: prototypeCellIndexPath)
}
}
// Use this proxy method when reusing static cells
override func tableView(_ tableView: UITableView,
indentationLevelForRowAt indexPath: IndexPath) -> Int
{
let section = indexPath.section
if sectionsDataModel[section].cellType == .staticCell {
return super.tableView(tableView, indentationLevelForRowAt: indexPath)
} else {
// Assign the indentationLevel of the prototype cell drawn in storyBoard to other cells
let prototypeCellIndexPath = IndexPath(row: 0, section: indexPath.section)
return super.tableView(tableView, indentationLevelForRowAt: prototypeCellIndexPath)
}
}
// Set the partition title
override func tableView(_ tableView: UITableView,
titleForHeaderInSection section: Int) -> String?
{
return sectionsDataModel[section].headerTitle
}
Copy the code
Optimization is finally complete!
Really not easy, see here is not a little dizzy, in fact, to sum up, split and implement the following module, write TableView can do very elegant, after the basic can be used in full set:
- Define the network request protocol
- Define tabular data model protocols
- Define the API data model
- Define the network request model
- Define the table data model
- Define the Cell data requirements model
- Defining the view model
- define
UITableViewCell
A subclass
The complete project has been uploaded to Github project address
Please visit my website to read more articles.
Photo: Return Trip from Hana, Hawaii – Luca Bravo @Unsplash