每次寫 TableView 都是又愛又恨,代碼感受老是很像,但細節不一樣又借鑑不了。究其緣由就是代碼沒有真正規範和模塊化。在參考了幾篇文章後,我總結了一個範式,能夠很大程度上對 TableView 的編寫作到規範化。本文不只是對 TableView 的總結,同時也是對協議、枚舉等類型的使用探討。ios
參考文章以下:git
本文重點從數據模型角度進行優化,TableView 的數據模型分爲三種狀況:動態類型 cell(Dynamic Prototypes
)、靜態類型 cell(Static Cells
)、動靜態混合類型。github
先看看優化後的整體模型結構圖:json
優化的關鍵在於配合協議和枚舉對模型的合理設計,下面咱們先看看動態類型:swift
咱們接着上次的示例工程進行改寫(上次教程 參見這裏 ,Github 示例工程地址)api
在上次的示例工程中咱們有一個展現書單的簡單列表,下面咱們將添加如下功能:數組
1.從豆瓣獲取我收藏的書籍
2.列表分爲 3 個 Sectinon 展現:想看、在看、已看的書
3.在列表中交替展現兩種類型的 Cell(即異構類型數據模型):書籍名稱、書籍評分
4.書籍評分的詳情頁中,將包含動靜態混合數據。網絡
最終效果以下:數據結構
URLSession
發送請求,咱們先添加兩個協議:?NetworkProtocol.swift
:在 send
方法中咱們使用泛型約束,這樣比直接使用 Request
協議做爲參數類型更高效。架構
/// 網絡請求發送協議 protocol Client { var host: String { get } func send<T: Request>(_ r: T, handler: @escaping (Data?) -> Void) } /// 網絡請求內容協議 protocol Request { var path: String { get } var method: HTTPMethod { get } var parameter: [String: Any] { get } }
添加兩個枚舉類型:
?Enums.swift
:
/// Http 請求方法 public enum HTTPMethod: String { case GET case POST } /// 主機類型 /// /// - doubanAPI: 豆瓣 API enum HostType: String { case doubanAPI = "https://api.douban.com" }
再添加對應協議的請求模型:
?URLSessionClient.swift
:在這裏咱們不解析返回的 Data 類型數據,後面交給數據模型來完成。
/// 網絡客戶端模型 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() } }
?BookRequest.swift
:在這裏咱們將請求獲得的 Data 類型數據經過 Swift 4.0 提供的 JSONDecoder
進行解析,這比之前解析 json
的方式優雅太多了,所以接下來咱們的數據模型都將要支持 Codable
協議,以使用此便利。代碼中的 BookCollections
數據模型將在後面建立。
/// 書籍查詢的網絡請求模型 struct BookRequest: Request { let userName: String // 用戶名 let status: String // 閱讀狀態:想讀:wish 在讀:reading 讀過:read let start: Int // 起始編號 let count: Int // 每次查詢最大數量 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") } } } }
Codable
協議中最美好的地方。?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 }
以上幾個模型也能夠稱爲 API 模型,真正應用到表格中還須要作轉換,爲了保持儘可能解耦,咱們這裏設計模型時儘可能保持做用分離。接下來咱們設計的數據模型是與表格的需求一一對應的,可是爲了達到這樣的對應,還須要一個轉換層,這裏咱們使用
ViewModel
來承擔模型轉換的責任。
?TableDataModelProtocol.swift
:
/// TableView 動態類型數據模型協議。 /// 包含全部 Cell 數據的二維數組(第一層對應 section,第二層對應 section 下的 cell), /// 以及 Section 數據數組 protocol TableViewDynamicCellDataModel { associatedtype TableDataModelType: CellModelType associatedtype SectionItem: TableViewSection var dynamicTableDataModel: [[TableDataModelType]] { get set } var sectionsDataModel: [SectionItem] { get set } } /// TableView section 信息結構體模型協議,包含 section 標題信息等。 protocol TableViewSection { var headerTitle: String? { get } var footerTitle: String? { get } } /// Cell 統一數據模型協議 protocol CellModelType { // 此爲包裝協議,便於在其餘協議中使用,能夠爲空。 }
?CellDataModelProtocol.swift
:
/// 書籍列表中的書名類 Cell 數據協議 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 } }
爲了讓 Cell 支持多種數據類型,咱們使用枚舉
BookListCellModelType
將異構類型變爲同構類型。對於將異構變爲同構,除了枚舉還可使用協議,將多個類型聽從同一個協議,可是使用時爲了區分不一樣類型代碼仍是不夠優雅。枚舉的好處是使用
switch
語句時能夠利用編譯器檢查,可是枚舉最大的缺點就是提取值時有點繁瑣,後面將會看到。然而最不建議的就是使用
Any
、AnyObject
。
?Enums.swift
:CellModelType
是包裝協議,協議自己是空的,只是爲了涵蓋全部的 Cell 數據類型枚舉。
/// 書籍列表 Cell 使用的數據模型類型 /// /// - bookInfo: 圖書基本信息 enum BookListCellModelType: CellModelType { case bookInfo(BookInfoCellModel) // 後續將方便擴展多個數據模型 }
?CellModel.swift
:書籍列表中的 Cell 因爲是可複用的,所以咱們須要把 identifier 經過枚舉的形式標明。
/// 書籍列表的書籍名稱類 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 = "" } /// 列舉表格中包含的全部動態 cell 標識符 public enum CellIdentifierType: String { case bookInfoCell }
?SectionModel.swift
:這裏的 cellType
、cellCount
是爲靜態表格預留的。
/// TableView 中的 section 數據結構體 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 類型 /// /// - staticCell: 靜態 /// - dynamicCell: 動態 public enum CellType: String { case staticCell case dynamicCell }
?TableViewViewModel.swift
:DataModel
是 API 解析後的模型,[[BookListCellModelType]]
是表格須要的數據模型,使用二維數組的形式是由於在 DataSource
代理方法中使用起來很是方便直接。
getTableDataModel
方法是用來進行數據結構包裝的。getBookInfo
方法是真正進行數據細節上的轉換的,若是有字段映射的變更在這裏修改就能夠了。
struct TableViewViewModel { /// 構造表格統一的數據模型 /// /// - Parameter model: 原始數據模型 /// - Returns: 表格數據模型 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] } /// 獲取 BookInfoCellModel 數據模型 /// /// - Parameter model: 原始數據子模型 /// - Returns: 統一的 cell 數據模型 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) } } /// 書籍閱讀狀態 public enum BookReadingStatus: String { case wish case reading case read case all = "" }
?MainTableViewController.swift
:loadData()
方法加載完數據後會自動刷新表格
/// 數據源對象 var dynamicTableDataModel: [[BookListCellModelType]] = [] { didSet { tableView.reloadData() } } override func viewDidLoad() { super.viewDidLoad() loadData() // 加載數據 // ... } /// 加載初始數據 func loadData() { 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 } }
?MainTableViewController+DataSource.swift
:下面代碼幾乎能夠無痛移植到另外一個 TabelView 中,在代碼註釋的地方改個名便可。這樣的 DataSource
是否是清爽多了,這樣的代碼寫一遍基本就能夠不用理了。其中有個 configureCell
方法,咱們通通移到自定義 Cell 類文件中實現,讓 Cell 完成本身的配置,餵給 Cell 的數據都是不用轉換的,多虧了協議的功勞(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): // bookInfo 移植更名 let identifier = bookInfo.identifier.rawValue // bookInfo 移植更名 let cell = tableView.dequeueReusableCell( withIdentifier: identifier, for: indexPath) as! BookInfoCell // BookInfoCell 移植更名 cell.configureCell(model: bookInfo) // bookInfo 移植更名 return cell } }
?Enums.swift
:增長評分信息的 Cell 數據類型。
/// 書籍列表 Cell 使用的數據模型類型 /// /// - bookInfo: 圖書基本信息 /// - bookRating: 圖書評分信息 enum BookListCellModelType: CellModelType { case bookInfo(BookInfoCellModel) case bookRating(BookRatingCellModel) }
?CellDataModelProtocol.swift
:增長評分信息的 Cell 數據協議。
/// 書籍列表中的評分類 Cell 數據協議 protocol BookRatingCellProtocol { var identifier: CellIdentifierType { get } var average: String { get } var numRaters: String { get } var id: String { get } var title: String { get } }
?CellModel.swift
:增長評分類 cell 的數據結構體
/// 書籍列表的書籍評分類 cell 的數據結構體 struct BookRatingCellModel: BookRatingCellProtocol { var identifier = CellIdentifierType.bookRatingCell var average: String = "" var numRaters: String = "" var id: String = "" var title: String = "" }
?TableViewViewModel.swift
:VM 中再增長下數據模型轉換方法:
static func getTableDataModel(model: DataModel) -> [[BookListCellModelType]] { ... let bookRating = getBookRating(model: myBook.book) switch status { case .wish: bookWishToRead.append(bookInfo) bookWishToRead.append(bookRating) // 增長的數據類型 case .reading: bookReading.append(bookInfo) bookReading.append(bookRating) // 增長的數據類型 case .read: bookRead.append(bookInfo) bookRead.append(bookRating) // 增長的數據類型 case .all: break } ... } /// 獲取 BookRatingCellModel 數據模型 /// /// - Parameter model: 原始數據子模型 /// - Returns: 統一的 cell 數據模型 static func getBookRating(model: Book) -> BookListCellModelType { var cellModel = BookRatingCellModel() cellModel.average = "評分:" + model.rating.average cellModel.numRaters = "評價人數:" + String(model.rating.numRaters) cellModel.id = model.id cellModel.title = model.title return BookListCellModelType.bookRating(cellModel) }
最後一步,DataSource
微調一下:
?MainTableViewController+DataSource.swift
:添加一個 case 便可,編譯器還會自動提示你,使用枚舉封裝異構數據類型是否是很爽?.
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 // 新增數據類型部分: 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 } }
詳情頁是純靜態類型表格,用相似的方式封裝協議和模型,更是簡潔到無以復加。
?CellDataModelProtocol.swift
:增長詳情頁 Cell 的數據模型協議
/// 圖書詳情類 Cell 數據協議 protocol BookDetailCellProtocol { var title: String { get } var authors: String { get } var publisher: String { get } }
?CellModel.swift
:增長詳情頁 Cell 的數據模型
/// 書籍詳情類 cell 的數據結構體 struct BookDetailCellModel: BookDetailCellProtocol { var title: String = "" var authors: String = "" var publisher: String = "" }
?TableViewViewModel.swift
:增長書籍詳情的模型轉換
/// 獲取 BookDetailCellModel 數據模型 /// /// - Parameter model: BookInfoCellModel 數據模型 /// - Returns: BookDetailCellModel 數據模型 static func getBookDetail(model: BookInfoCellModel) -> BookDetailCellModel { var cellModel = BookDetailCellModel() cellModel.title = model.title cellModel.authors = model.authors cellModel.publisher = model.publisher return cellModel }
?TableDataModelProtocol.swift
:增長靜態類型 TableView 數據模型協議
/// TableView 靜態類型數據模型協議。 /// 包含 Cell 結構體數據、 Section 數據數組 protocol TableViewStaticCellDataModel { associatedtype StaticCellDataModel associatedtype SectionItem: TableViewSection var staticTableDataModel: StaticCellDataModel { get set } var sectionsDataModel: [SectionItem] { get set } }
?DetailTableViewController.swift
:最後集成一下,一共 40 行,是否是很簡潔?.
import UIKit class DetailTableViewController: UITableViewController, TableViewStaticCellDataModel { // MARK: 1.--@IBOutlet屬性定義-----------? @IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var authorsLabel: UILabel! @IBOutlet weak var publisherLabel: UILabel! // MARK: 2.--實例屬性定義----------------? var staticTableDataModel = BookDetailCellModel() var sectionsDataModel: [SectionModel] = [] // MARK: 3.--視圖生命週期----------------? override func viewDidLoad() { super.viewDidLoad() setSectionDataModel() // 設置 section 數據模型 configureCell(model: self.staticTableDataModel) // 配置 Cell 顯示內容 } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } // MARK: 4.--處理主邏輯-----------------? /// 設置 section 數據模型 func setSectionDataModel() { sectionsDataModel = [SectionModel(headerTitle: nil, footerTitle: nil, cellCount: 3)] } /// 配置靜態 Cell 顯示內容 func configureCell<T: BookDetailCellProtocol>(model: T) { nameLabel?.text = model.title authorsLabel?.text = model.authors publisherLabel?.text = model.publisher } // MARK: 5.--數據源方法------------------? override func numberOfSections(in tableView: UITableView) -> Int { return sectionsDataModel.count } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return sectionsDataModel[section].cellCount } }
功能 4 要實現一個動靜態混合的表格,這個話題也是 TabelView 改造中很常見的一個話題。我嘗試了幾種方案,總想用點黑科技偷點懶,實驗完發現仍是得用常規思路。現總結以下:
UITableViewCell
子類和配套的 xib
文件,添加 Identifier
,這個用來複用動態 cell 部分。開動吧。
?Enums.swift
:增長點類型
/// 列舉表格中包含的全部動態 cell 標識符 public enum CellIdentifierType: String { case bookInfoCell case bookRatingCell case bookReviewTitleCell } /// Cell nib 文件類型 public enum CellNibType: String { case BookReviewListTableViewCell } /// 書籍評論列表頁的評分項 Cell 使用的數據模型類型 enum BookReviewCellModelType: CellModelType { case bookReviewList(BookReviewListCellModel) }
?TableDataModelProtocol.swift
:建立一個混合版數據模型協議,其實就是合併了動態、靜態的數據類型
/// TableView 動態、靜態混合類型數據模型協議。 /// 包含動態 Cell 二維數組模型、靜態 Cell 結構體數據、Section 數據數組、動態 Cell 的複用信息。 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 } }
?CellDataModelProtocol.swift
:Cell 的數據模型協議分爲動態、靜態兩部分。
/// 圖書評論摘要列表數據協議 protocol BookReviewListCellProtocol { var identifier: CellIdentifierType { get } var title: String { get } var rate: String { get } var link: String { get } } /// 圖書評論標題數據協議 protocol BookReviewHeadCellProtocol { var title: String { get } var rate: String { get } }
?BookReviewRequest.swift
:評論數據須要單獨發起網絡請求,新增一種請求模型便可。
struct BookReviewRequest: Request { let bookID: String // 書籍 ID let start: Int // 起始編號 let count: Int // 每次查詢最大數量 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") } } } }
?DataModel.swift
:API 模型也相應新增。
struct BookReview: Codable { var reviews: [Review] = [] struct Review: Codable { var rating: Score var title: String = "" var alt: String = "" // 評論頁連接 } struct Score: Codable { var value: String = "" } }
?CellModel.swift
:實現 cell 數據協議所需的模型,也按動態、靜態區分開。
/// 書籍評論詳情的摘要列表類 cell 的數據結構體 struct BookReviewListCellModel: BookReviewListCellProtocol { var identifier = CellIdentifierType.bookReviewTitleCell var title: String = "" var rate: String = "" var link: String = "" } /// 書籍評論詳情的評論標題類 cell 的數據結構體 struct BookReviewHeadCellModel: BookReviewHeadCellProtocol { var id: String = "" var title: String = "" var rate: String = "" }
?TableViewViewModel.swift
:VM 中再把 API 模型轉換到 cell 數據模型。
/// 獲取 BookReviewListCellModel 數據模型 /// /// - Parameter model: BookReview 數據模型 /// - Returns: 書籍評論頁須要的評論列表模型 static func getBookReviewList(model: BookReview) -> [[BookReviewCellModelType]] { var cellModel: [BookReviewCellModelType] = [] for review in model.reviews { var bookReviewListCellModel = BookReviewListCellModel() bookReviewListCellModel.title = review.title bookReviewListCellModel.rate = "評分:" + review.rating.value bookReviewListCellModel.link = review.alt // 轉換爲 enum 類型 let model = BookReviewCellModelType.bookReviewList(bookReviewListCellModel) cellModel.append(model) } return [[], cellModel] } /// 獲取 BookReviewHeadCellModel 數據模型 /// /// - Parameter model: Book 數據模型 /// - Returns: 書籍評論頁須要的標題信息 static func getBookReviewHead(model: BookRatingCellModel) -> BookReviewHeadCellModel { var cellModel = BookReviewHeadCellModel() cellModel.id = model.id cellModel.title = model.title cellModel.rate = model.average return cellModel }
?ReviewTableViewController.swift
:最後的集成,靜態部分的 Cell 直接把要設置的控件創建 IBOutlet,用數據模型映射一下就好。
class ReviewTableViewController: UITableViewController, TableViewMixedCellDataModel { // MARK: 1.--@IBOutlet屬性定義-----------? @IBOutlet weak var bookNameLabel: UILabel! @IBOutlet weak var rateLabel: UILabel! // MARK: 2.--實例屬性定義----------------? /// 數據源對象 var dynamicTableDataModel: [[BookReviewCellModelType]] = [] { didSet { if shouldReloadTable { setSectionDataModel() tableView.reloadData() } } } var staticTableDataModel = BookReviewHeadCellModel() var sectionsDataModel: [SectionModel] = [] var cellNibs: [(CellNibType, CellIdentifierType)] = [(.BookReviewListTableViewCell, .bookReviewTitleCell)] /// 有數據更新時是否容許刷新表格 var shouldReloadTable: Bool = false
再進行一些初始化設置,注意:在 viewDidLoad
方法中就已經能夠對靜態 cell 經過 IBOutlet
進行配置了。
// MARK: 3.--視圖生命週期----------------? override func viewDidLoad() { super.viewDidLoad() loadData() // 加載數據 setSectionDataModel() // 設置 section 數據模型 configureStaticCell(model: staticTableDataModel) // 配置 Cell 顯示內容 setupView() // 視圖初始化 } // MARK: 4.--處理主邏輯-----------------? /// 加載初始數據 func loadData() { 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 } } /// 設置 section 數據模型 func setSectionDataModel() { let section1 = SectionModel( headerTitle: "書籍", footerTitle: nil, cellType: .staticCell, cellCount: 2) var section2CellCount = 0 if dynamicTableDataModel.count > 0 { section2CellCount = dynamicTableDataModel[1].count } let section2 = SectionModel( headerTitle: "精選評論", footerTitle: nil, cellType: .dynamicCell, cellCount: section2CellCount) sectionsDataModel = [section1, section2] } /// 配置靜態 Cell 顯示內容 func configureStaticCell<T: BookReviewHeadCellProtocol>(model: T) { bookNameLabel?.text = model.title rateLabel?.text = model.rate } /// 視圖初始化相關設置 func setupView() { // 註冊 cell nib 文件 for (nib, identifier) in cellNibs { let nib = UINib(nibName: nib.rawValue, bundle: nil) tableView.register(nib, forCellReuseIdentifier: identifier.rawValue) } }
關鍵的數據源方法來了:
// MARK: 8.--數據源方法------------------? override func numberOfSections(in tableView: UITableView) -> Int { return sectionsDataModel.count } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return sectionsDataModel[section].cellCount }
咱們在 sectionsDataModel
的數據中就已經包含了 Cell 的動靜態類型,所以能夠直接拿來判斷。靜態類型的 Cell 經過 super
屬性便可直接獲取, super
其實就是控制器對象自己,從中獲取的 Cell 是從 StoryBoard
中初始化過的實例,這樣獲取能夠避免 cellForRowAt
再調用自身方法形成死循環。動態類型 Cell 直接調用 dequeueReusableCell
方法便可,注意要帶 for: indexPath
參數的那個。
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 } } }
視圖代理方法中還有一些要補充的,這些方法是因爲套用靜態 TableView 來實現動態 cell 效果帶來的反作用,照着寫就行:
// MARK: 9.--視圖代理方法----------------? // 複用靜態 cell 時要使用這個代理方法 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) } } // 複用靜態 cell 時要使用這個代理方法 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 { // 將 storyBoard 中繪製的原型 cell 的 indentationLevel 賦予其餘 cell let prototypeCellIndexPath = IndexPath(row: 0, section: indexPath.section) return super.tableView(tableView, indentationLevelForRowAt: prototypeCellIndexPath) } } // 設置分區標題 override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return sectionsDataModel[section].headerTitle }
真不容易,看到這裏是否是有點暈,其實總結一下,拆分並實現如下模塊,編寫 TableView
就能夠作到很優雅了,之後基本就能夠全套套用了:
UITableViewCell
子類完整工程已上傳 Github 工程地址
歡迎訪問 個人我的網站 ,閱讀更多文章。