Swift iOS : 一個簡陋的TableView封裝

UITableView功能強大,可是使用delegate設計模式的DataSource真的很不舒服。好比說:javascript

  1. 一堆冗長的函數簽名
  2. 只能拷貝,錯一點都沒法執行的,也不會提示你不對

冗長的函數簽名是這樣的:java

func numberOfSections(in: UITableView) -> Int 
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 複製代碼

是否能夠給它一個DataSource對象,它本身就能夠顯示內容便可呢,就像這樣:swift

tableview.Datasource = [["java","swift","js"],["java","swift","js"]]設計模式

它應該能夠數組

  1. 本身提取發現有兩個section
  2. 每一個section內的row的數量
  3. 以及要顯示到Cell的內容。

這是可能的,實際上,以下類Table封裝完畢,使用的時候,就是能夠達成但願的效果的,使用Table,你沒必要在本身編寫這些函數,代碼以下:app

import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        self.window = UIWindow(frame: UIScreen.main.bounds)
        let page = Page()
        page.view.backgroundColor = .blue
        self.window!.rootViewController = page
        self.window?.makeKeyAndVisible()
        return true
    }
}
class Page: UIViewController {
    var a : Table!
    override func viewDidLoad() {
        super.viewDidLoad()
        a  = Table()
        a.ds = [["java","swift","js"],["java","swift","js"]]
        a.frame = CGRect(x: 0,y: 50,width: 300,height: 500)
        self.view.addSubview(a)
    }
}
class Table : UITableView,UITableViewDataSource,UITableViewDelegate{
    public var  ds : [[Any]]
    override init(frame: CGRect, style: UITableViewStyle) {
        ds = []
        super.init(frame:frame,style:style)

        self.dataSource = self
        self.delegate = self
    }
    required init?(coder aDecoder: NSCoder) {
        ds = []
        super.init(coder:aDecoder)
    }
    func numberOfSections(in: UITableView) -> Int {
        return ds.count
    }
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 44
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return ds[section].count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let arr = ds
        let a = UITableViewCell(style: .default, reuseIdentifier: nil)
        a.textLabel?.text = String(describing:arr[indexPath.section][indexPath.row])
        return a
    }
}複製代碼

這個案例有不少限制,好比數據源內的數據項,只能是String類型。若是想要通常的對象做爲數據項,更加花哨的Cell做爲外觀,有辦法嗎?關鍵就是,讓Cell的類型是參數化的,能夠傳遞和修改的。爲此,咱們須要在Table內加入一個cellClass的類,它能夠由使用這套封裝的開發者傳遞進來。以下案例,已經完成了此工做,並提供了對象化的數據項做爲案例:框架

import UIKit
 @UIApplicationMain
 class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        self.window = UIWindow(frame: UIScreen.main.bounds)
        let page = Page()
        page.view.backgroundColor = .blue
        self.window!.rootViewController = page
        self.window?.makeKeyAndVisible()
        return true
    }
 }
 class Page: UIViewController {
    var a : Table!
    override func viewDidLoad() {
        super.viewDidLoad()
        var s = DoubleString("Paris","Charles de Gaulle")
        var t = DoubleString("Rome","FCO")
        a  = Table()
        a.cellClass = DoubleStringCell.self
        a.ds = [[s,t]]
        a.load()
        a.frame = CGRect(x: 0,y: 50,width: 300,height: 500)
        self.view.addSubview(a)
    }
 }
 class DoubleString{
    var s1 : String
    var s2 : String
    init(_ s1:String,_ s2:String){
        self.s1=s1
        self.s2=s2
    }
 }
 class DoubleStringCell:Cell{
    var l1 : UILabel?=UILabel()
    var l2 : UILabel?=UILabel()
    override func layoutSubviews() {
        l1?.frame = CGRect(x: 0, y: 0,width: 200,height: 20)
        self.addSubview(l1!)
        l2?.frame = CGRect(x: 0, y: 25,width: 200,height: 20)
        self.addSubview(l2!)
    }
    override  func loadData(_ obj : Any){
        let ds = obj as! DoubleString
        l1?.text = ds.s1
        l2?.text = ds.s2
    }
 }
 class StringCell:Cell{
    override  func loadData(_ obj : Any){
        textLabel?.text = "\(obj)"
    }
 }
 // Framework Zone
 class Table: UITableView,UITableViewDataSource,UITableViewDelegate{
    var ds_ : [[Any]]?
    public var  ds : [[Any]]?{
        get{return ds_}
        set{
            ds_ = newValue
        }
    }
    var  cellClass_ : AnyClass?
    public var  cellClass : AnyClass?{
        get{return cellClass_}
        set{cellClass_ = newValue}
    }
    func load(){
        self.dataSource = self
        self.delegate = self
        register( cellClass, forCellReuseIdentifier: "\(cellClass)");
        print("\(cellClass)")
    }
    func numberOfSections(in: UITableView) -> Int {
        return ds!.count
    }
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 44
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return ds![section].count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let arr = ds!
        let a  = dequeueReusableCell(withIdentifier: "\(cellClass)", for: indexPath) ;
        loadCell(a,arr[indexPath.section][indexPath.row])
        return a
    }
    func loadCell(_ cell : UITableViewCell,_ item : Any){
        (cell as! Cell).loadData(item)
    }
}

 class Cell : UITableViewCell{
    public func loadData(_ obj : Any){
        textLabel?.text = "implements yourself cell for \(obj)"
    }
 }複製代碼

繼承了Cell,而且實現了loadData,就能夠在loadData內本身加載任何傳入進來的數據項對象,這裏的數據項對象是DoubleString,內部承載了兩個字符串。固然還能夠是任何類型的對象,反正在本身的loadData內本身編寫加載數據項對象就能夠了。ide

固然,這段代碼依然並不理想,由於loadData的實現,依然要求開發者本身覆蓋基礎類Cell的loadData。這樣的耦合並不討喜。最好是此處的函數也能夠參數化,方法就是讓開發者本身傳遞一個事件函數進來,須要時,框架調用用戶的事件函數:函數

import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        self.window = UIWindow(frame: UIScreen.main.bounds)
        let page = Page()
        page.view.backgroundColor = .blue
        self.window!.rootViewController = page
        self.window?.makeKeyAndVisible()
        return true
    }
}
class Page: UIViewController {
    var a : Table!
    override func viewDidLoad() {
        super.viewDidLoad()
        var s = DoubleString("Paris","Charles de Gaulle")
        var t = DoubleString("Rome","FCO")
        a  = Table()
        a.onCellData = {(cell ,obj) in
            let ds = obj as! DoubleString
            let c = cell as! DoubleStringCell
            c.l1?.text = ds.s1
            c.l2?.text = ds.s2
        }
        a.cellClass = DoubleStringCell.self
        a.ds = [[s,t]]
        a.load()
        a.frame = CGRect(x: 0,y: 50,width: 300,height: 500)
        self.view.addSubview(a)
    }
}
class DoubleString{
    var s1 : String
    var s2 : String
    init(_ s1:String,_ s2:String){
        self.s1=s1
        self.s2=s2
    }
}
class DoubleStringCell:UITableViewCell{
    var l1 : UILabel?=UILabel()
    var l2 : UILabel?=UILabel()
    override func layoutSubviews() {
        l1?.frame = CGRect(x: 0, y: 0,width: 200,height: 20)
        self.addSubview(l1!)
        l2?.frame = CGRect(x: 0, y: 25,width: 200,height: 20)
        self.addSubview(l2!)
    }
}
// Framework Zone
typealias  OnCellData = (_ cell : UITableViewCell ,_ obj : Any)->Void
class Table: UITableView,UITableViewDataSource,UITableViewDelegate{
    var ds_ : [[Any]]?
    var onCellData_ : OnCellData?
    var onCellData : OnCellData?{
        get{return onCellData_}
        set{
            onCellData_ = newValue
        }
    }
    public var  ds : [[Any]]?{
        get{return ds_}
        set{
            ds_ = newValue
        }
    }
    var  cellClass_ : AnyClass?
    public var  cellClass : AnyClass?{
        get{return cellClass_}
        set{cellClass_ = newValue}
    }
    func load(){
        self.dataSource = self
        self.delegate = self
        register( cellClass, forCellReuseIdentifier: "\(cellClass)");
        print("\(cellClass)")
    }
    func numberOfSections(in: UITableView) -> Int {
        return ds!.count
    }
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 44
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return ds![section].count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let arr = ds!
        let a  = dequeueReusableCell(withIdentifier: "\(cellClass)", for: indexPath) ;
        loadCell(a,arr[indexPath.section][indexPath.row])
        return a
    }
    func loadCell(_ cell : UITableViewCell,_ item : Any){
        onCellData_?(cell,item)
    }
}複製代碼

本來須要繼承並覆蓋的loadData如今能夠轉移爲事件,從而砍掉一些不自在的代碼。此案例中,還有一處能夠稍微優化,就是viewDidLoad內的DoubleString,喜好Swift的字面量對象的話,能夠改寫爲數組,反正解析的時候,轉換爲數組就好:優化

override func viewDidLoad() {
    super.viewDidLoad()        
    a  = Table()
    a.onCellData = {(cell ,obj) in
        let ds = obj as! [String]
        let c = cell as! DoubleStringCell
        c.l1?.text = ds[0]
        c.l2?.text = ds[1]
    }
    a.cellClass = DoubleStringCell.self
    a.ds = [[["Paris","Charles de Gaulle"],["Rome","FCO"]]]
    a.load()
    a.frame = CGRect(x: 0,y: 50,width: 300,height: 500)
    self.view.addSubview(a)
}複製代碼
相關文章
相關標籤/搜索