[Swift] UITableView:从牛刀到小刀 (1) - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
iOS 开发实用技术导航
NSHipster 中文版
http://nshipster.cn/
cocos2d 开源 2D 游戏引擎
http://www.cocos2d-iphone.org/
CocoaPods
http://cocoapods.org/
Google Analytics for Mobile 统计解决方案
http://code.google.com/mobile/analytics/
WWDC
https://developer.apple.com/wwdc/
Design Guides and Resources
https://developer.apple.com/design/
Transcripts of WWDC sessions
http://asciiwwdc.com
Cocoa with Love
http://cocoawithlove.com/
Cocoa Dev Central
http://cocoadevcentral.com/
NSHipster
http://nshipster.com/
Style Guides
Google Objective-C Style Guide
NYTimes Objective-C Style Guide
Useful Tools and Services
Charles Web Debugging Proxy
Smore
banxi1988
V2EX    iDev

[Swift] UITableView:从牛刀到小刀 (1)

  •  
  •   banxi1988
    banxi1988 2016-12-28 21:28:06 +08:00 3388 次点击
    这是一个创建于 3284 天前的主题,其中的信息可能已经有所发展或是发生改变。

    对于 iOS 开发来说, UITableView 无疑是一把牛刀. 今天我想介绍的是怎么让这把牛刀像小刀一样好使.

    我们想实现的目标是, 对于一些简单的场景,比如一个小的列表,可以像如下这么使用:

     let dataSource = ListDataSource(dataItems:[item1,item2,...]) tableView.dataSource = dataSource 

    OK, 我们开始吧.

    创建一个实现了 UITableViewDataSource 协议的类.

    由于 UITableViewDataSource 协议扩展了 NSObjectProtocl, 所以我们的类最好继承自 NSObject.

    class ListDataSource: NSObject, UITableViewDataSource{ } 

    实现 UITableViewDataSource 要求的两个方法:

    class ListDataSource: NSObject, UITableViewDataSource{ public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ return 0 } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{ return UITableViewCell(style: .default, reuseIdentifier: "cell") } } 

    这里就遇到问题了,我们的数据是动态的,不能直接返回 0. 我们的 cell 也不是这样直接一个 UITableViewCell 类啊. 问题是,我们不知道数据是什么,怎么构造 Cell?

    引入泛型数组.

    • 先来处理 numberOfRowsInSection 这个方法. 这里我们只用到一个 Section, 我们的数据最常见的是一个数组. 那返回这个数组的大小即可. 但是数组类型我们不知道, 于是我们声明为泛型 . 如下:

    于是 ListDataSource 变成了这样:

    public class ListDataSource<T>: NSObject, UITableViewDataSource{ public private(set) var dataItems: [T] = [] public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ return dataItems.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{ return UITableViewCell(style: .default, reuseIdentifier: "cell") } } 

    引入泛型 UITableViewCell 子类.

    解决了上面问题, 同理我们的 UITableViewCell 是不是也可以声明为泛型呢?

    于是变成这样:

    public class ListDataSource<T,Cell: UITableViewCell>: NSObject, UITableViewDataSource{ public private(set) var dataItems: [T] = [] public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ return dataItems.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{ return Cell(style: .default, reuseIdentifier: "cell") } } 

    上面我们对于 Cell 泛型也增加了基本的限制,就是 Cell 是继承自 UITableViewCell 的.

    引入 Cell 数据绑定协议

    但是这里有一个问题就是, 我们的数据跟 Cell 还没有关联起来呢! 也就是说我们需要 Cell 有关联 数据模型 T 的能力. 上面这个需求体现了两点, 一表明某个能力, 即满足某协议约束. 对于泛型来说,可以增加需要实现的协议作为约束.

    于是我们要求,Cell 应该实现如下协议, 可以绑定某一个数据.

    public protocol Bindable{ associatedtype DataItem func bind(to item: DataItem) } 

    因为我们不知道要绑定的数据类型是什么, 所以我们只能先给一个名字. DataItem. 由实现者确定类型是什么. 也相当是一种泛型.

    同时这个 DataItem 应该等于之前的数据泛型 T. 这样,我们得到如下的 ListDataSource

    public protocol Bindable{ associatedtype DataItem func bind(to item: DataItem) } public class ListDataSource<T,Cell: UITableViewCell>: NSObject, UITableViewDataSource where Cell: Bindable, Cell.DataItem == T{ public private(set) var dataItems: [T] = [] public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ return dataItems.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{ let cell = Cell(style: .default, reuseIdentifier: "cell") cell.bind(to: dataItems[indexPath.row]) return cell } } 

    重点在于 where Cell: Bindable, Cell.DataItem == T 也就是我们上面所说的. 要求 Cell 实现 Bindable 协议.

    简化 泛型声明. 去掉泛型 T 声明.

    之前由于不确定数据模型的类型,所以我们引入了 T 作为泛型名. 但是后来我们又添加了 Bindable, 其中使用 DataItem 名来标识数据类型. 这里两者相等,看起来重复了. 简化后如下:

    public protocol Bindable{ associatedtype DataItem func bind(to item: DataItem) } public class ListDataSource<Cell: UITableViewCell>: NSObject, UITableViewDataSource where Cell: Bindable{ public private(set) var dataItems: [Cell.DataItem] = [] public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ return dataItems.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{ let cell = Cell(style: .default, ruseIdentifier: "cell") cell.bind(to: dataItems[indexPath.row]) return cell } } 

    完善一下.

    • 支持 ReuseableCell 模型.
    • 支持 自动注册重用 Cell. 完善后的代码如下:
    public protocol Bindable{ associatedtype DataItem func bind(to item: DataItem) } extension String{ public static let listCellReuseIdentifier = "listCell" } public class ListDataSource<Cell: UITableViewCell>: NSObject, UITableViewDataSource where Cell: Bindable{ public private(set) var dataItems: [Cell.DataItem] = [] private weak var tableView:UITableView? public init(dataItems: [Cell.DataItem]){ self.dataItems = dataItems } public func bind(to tableView: UITableView){ self.tableView = tableView tableView.dataSource = self tableView.register(Cell.classForCoder(), forCellReuseIdentifier: .listCellReuseIdentifier) } public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ return dataItems.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{ let cell = tableView.dequeueReusableCell(withIdentifier: .listCellReuseIdentifier, for: indexPath) as! Cell cell.bind(to: dataItems[indexPath.row]) return cell } } 

    使用示例

    有了上面的代码之后, 我们再显示一个 List 就简单得多了. 例如我们显示一个菜单列表.

    // 先定义菜单 Cell, 这是数据模型类型是 String class MenuCell: UITableViewCell,Bindable{ typealias DataItem = String func bind(to item: String) { textLabel?.text = item } } // 实例化一个 ListDataSource let dataSource = ListDataSource<MenuCell>(dataItems: [ "排骨面","红椒面", "青椒面" ]) let tableView = UITableView(frame: .zero, style: .plain) // 将 DataSource 与 UITableView 绑定. dataSource.bind(to: tableView) 

    怎么样? 最后实现的结果 ,跟我们的希望应该是差不多的了. 当然这只是一个开始, 为了适用你项目的使用, 你可以在上面的代码的基础上. 添加修改. Hope to see your advice!

    4 条回复    2016-12-29 09:22:05 +08:00
    HuangLibo
        1
    HuangLibo  
       2016-12-28 21:48:09 +08:00
    table view 是牛刀, 那 collection view 岂不是核武器了
    FreshOldMan
        2
    FreshOldMan  
       2016-12-28 21:49:10 +08:00
    大哥,每天一篇啊
    banxi1988
        3
    banxi1988  
    OP
       2016-12-28 21:59:25 +08:00
    @HuangLibo 怎么说呢. UICollectionView 从机制或者说潜力上来说, 比 TableView 强大.
    但是不如 UITableView 易用. UITableView 在很多使用场景下,基本是开箱即用. 而 UICollectionView 还需要我们自己多加加工.
    HuangLibo
        4
    HuangLibo  
       2016-12-29 09:22:05 +08:00   1
    @banxi1988 开箱即用,我推荐 IGListKit ,封装了 collection view
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2643 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 12:15 PVG 20:15 LAX 04:15 JFK 07:15
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86