優雅的使用UITableView(Swift 中)

xiAo-ju發表於2018-01-28

優雅的使用UITableView(OC 上)中,已經給大家分享了怎麼使用UITableView,優雅的構建一個頁面。

在這一節,主要和大家聊一聊這兩點:

  1. 怎麼把優雅的使用UITableView(OC 上)的思路搬到Swift
  2. 泛型Any的區別
  3. 在Swift中優雅的使用UITableView構建List頁面

怎麼把優雅的使用UITableView(OC 上)的思路搬到Swift

再回憶一下這張圖

優雅的使用UITableView(Swift 中)

其中關鍵的點其實就是Row,如果我們把Row做好了,其實大功基本告成。

看看成果:

優雅的使用UITableView(Swift 中)

Swift版的是不是更優雅了些?

Row的實現

struct NoneItem {}

protocol Updatable: class {
    
    associatedtype ViewData
    
    func update(viewData: ViewData)
}

extension Updatable {
    func update(viewData: NoneItem) {}
}

protocol RowType {
    
    var tag: RowTag { get }

    var reuseIdentifier: String { get }
    var cellClass: AnyClass { get }

    func update(cell: UITableViewCell)

    func cell<C: UITableViewCell>() -> C
    func cellItem<M>() -> M
}

class Row<Cell> where Cell: Updatable, Cell: UITableViewCell {

    let tag: RowTag

    let viewData: Cell.ViewData
    let reuseIdentifier = "\(Cell.classForCoder())"
    let cellClass: AnyClass = Cell.self
    
    init(viewData: Cell.ViewData, tag: RowTag = .none) {
        self.viewData = viewData
        self.tag = tag
    }

    func cell<C: UITableViewCell>() -> C {
        guard let cell = _cell as? C else {
            fatalError("cell 型別錯誤")
        }
        return cell
    }
    
    func cellItem<M>() -> M {
        guard let cellItem = viewData as? M else {
            fatalError("cellItem 型別錯誤")
        }
        return cellItem
    }

    private var _cell: Cell?

    func update(cell: UITableViewCell) {
        if let cell = cell as? Cell {
            self._cell = cell
            cell.update(viewData: viewData)
        }
    }
}

extension Row: RowType {}

public class RowTags {
    fileprivate init() {}
}

public class RowTag: RowTags {
    public let _key: String
    
    public init(_ key: String) {
        self._key = key
        super.init()
    }
}

extension RowTag: Hashable {
    public static func ==(lhs: RowTag, rhs: RowTag) -> Bool {
        return lhs.hashValue == rhs.hashValue
    }

    public var hashValue: Int {
        return _key.hashValue
    }
}

extension RowTags {
    static let none = RowTag("")
}
複製程式碼

以上程式碼,對比OC實現主要有三點不同:

  • CellClass從引數變為泛型
  • RowType的存在
  • RowTags(主要用來取Row,代替IndexPath)

不知道你對RowType這個協議的存在是否感到疑惑,假如沒有它行不行?

優雅的使用UITableView(Swift 中)

如果沒有RowType這個協議,這兩個row應該放在什麼型別的陣列裡呢?

你打算用Any?那你的程式碼裡肯定會出現一堆as? 的程式碼,顯然與我們談到的優雅背道而馳。

其實RowType的存在就是這些元素的抽象,讓我們知道這些元素的共同屬性。

是不是典型的面相協議程式設計?


泛型Any的區別

如果沒有怎麼接觸過Swift的同學,或者不太瞭解泛型的同學,看到上面的語法,肯定是一臉的懵逼。

在這裡簡單給不太瞭解的同學普及一下。

泛型,泛型,從字面理解就是廣泛的型別嘛,就是各種姿勢都滿足,但是他和Any有什麼不同呢?

我們先來看這麼一個需求,我想寫一個max函式,他要使用各種型別,如果沒有接觸過泛型的同學寫出來的函式應該是這樣(請只看方法定義)

func anyMax(_ x: Any, _ y: Any) -> Any {
    ....
}
複製程式碼

如果對於OC那樣指標操作的語言這似乎沒有問題,但是這對於Swift這樣的強型別語言就很有問題了。

為什麼?

假如我比較兩個Int型別的數字,返回的是Any,這顯然不是我想要的

let n = 1
let m = 2
// result 的型別會為Any
let result = anyMax(n, m)
複製程式碼

再看泛型版本

func genericMax<T>(_ x: T, _ y: T) -> T {
    ....
}
複製程式碼
let n = 1
let m = 2
// result 的型別會為Int
let result = genericMax(n, m)
複製程式碼

因為Swift有型別推斷,所以我們在輸入值比較時就知道了我們的result型別為Int

那麼我們可以總結出泛型Any的最大區別就是:

  • 泛型

    • 輸入與輸出結果型別一致
    • 延遲型別的確定
  • Any會造成型別丟失

Swiftmax函式的實現

優雅的使用UITableView(Swift 中)

where關鍵字表示約束條件,T必須為遵循了協議Comparable的型別


在Swift中優雅的使用UITableView構建List頁面

再看一遍這張圖

優雅的使用UITableView(Swift 中)

這有三組樣式的UITableView

優雅的使用UITableView(Swift 中)

其實ListDetail維護的東西是一樣的,就是那個RowContainer

核心程式碼

優雅的使用UITableView(Swift 中)

尾巴

在此OC和Swift的優雅使用UITabelView都已經和大家介紹完畢了。

下一節會和大家分享一下在我開發中,對Detail介面的運用和List介面的運用,以及怎麼用泛型去對Detail模型和List模型的解析。

在上一節中,有很多同學給我推薦了一些表單的庫,其實我自己也知道有很多優秀的表單庫,列如EurekaXLForm等等。

那麼,我為什麼還要自己造輪子?

兩個主要的原因:

  • 那些庫都太
  • 都只支援Detail,List介面沒有辦法相容

其實我也只是站在了大佬的肩上而已


在上一節中看到評論中主要有兩個問題:

  • 我用了響應鏈的傳值方式,怎麼傳遞引數?
func buttonAction(_ sender: UIButton) {
        (self.viewController as? ButtonCellActionable)?.buttonAction(sender, cell: self)
    }
複製程式碼
  • 在執行懶載入UIbutton時,self.viewController為nil,為什麼事件還能相應?

優雅的使用UITableView(Swift 中)

其實為nil,在我開發時,我是知道的,但我錯誤的理解為,系統會在執行時再去拿那個target。

為nil的原因其實是button還沒有新增在superView上,響應鏈還找不到他的UIViewContoller。

那麼既然,target沒有被系統持有,那麼,為什麼事件還能相應?

優雅的使用UITableView(Swift 中)

這就是UIKit中的定義,就是target為nil的時候,會走相應鏈 ,而我之前的實現,又恰好在VC中實現了,所以方法會被呼叫。

關於addTarget這個方法的更多事情,請看這裡

Demo

表單庫:

相關文章