前因:
直接用 UITableViewDataSource、UICollectionViewDataSource、UITableViewDelegate、UICollectionViewDelegate 数据源代理方法 去创建一个简单的 listView (以下统称 UITableView、UICollectionView 为 listView),步骤实在太麻烦而且没必要,浪费时间。
目录:
1、单一数据创建原则;
2、列表数据(cell数据)创建原则;
3、列表组头组尾数据创建规则;
4、写法上的注意点;
5、写法上的技巧;
6、基础总结;
7、在 cell、headerFooterView、reusableView 中接收传递的数据;
8、listView 代理数据源设置;
9、自适应高度的使用;
10、点击事件绑定;
11、最后说两句;
单一数据创建规则:
1、一个基于 NSObject 基类的对象代表一个单一数据;
2、一个数据对象 就 代表 一个列表中的一个 cell;
3、通过 .bind(@"类名"),指定创建 cell 的类名;
4、通过 .bindHeight(cell 高度),绑定 UITableViewCell 的高度;也可绑定 UICollectionViewCell 的高度,这样的话 UICollectionViewCell 的宽度则等于 UICollectionView 的宽度;
5、通过 .bindSize(item的size),绑定 UICollectionViewCell 的 itemSize; 若也使用 .bindHeight,优先级为 .bindSize > .bindHeight;
6、通过 .bindSpacing(mt_collectionViewSpacingMake(最小行距、最小间距、组间间距)),指定 UICollectionView 组的 行距,间距,间隔;
以上 提及的用法 使用任一 NSObject 对象,若为 数组对象,除会绑定本身外,还会顺带 绑定 数组元素;
7、若只想绑定 数组对象 而不顺带绑定数组元素,可使用 .arrBind(@"类名") 绑定 数组元素 cell类名;类似的还有 .arrBindSize(item的size),为仅对 数组对象本身绑定 size;
8、针对数组的用法 还有:.bindRowsHeight(@[高度])、.bindItemsSize(@[尺寸])、.bindItemsSpacing(@[mt_collectionViewSpacingMake(最小行距、最小间距、组间间距)]),用于对部分数组元素进行绑定;经实践证实 不常用,了解即可。
以上 提及的为 一个数据 绑定 cell 的创建原则;可参考本人的用法,如下:
object.bind(@"className").bindHeight(100); // UITableViewCell 或 UICollectionViewCell;
object.bind(@"className").bindSize(CGSizeMake(100, 100)); //UICollectionViewCell;
//如果不需要传入数据,可将 cell 的类名字符串绑定对应尺寸后直接传入,常用于开发时快速查看 cell样式,如下:
@"className".bindHeight(100);
@"className".bindSize(CGSizeMake(100, 100));
列表数据(cell数据)创建规则:
1、由 多个 单一 数据 构成的 数组对象,即代表 一个 完整列表 的数据;
2、一个 数组 对象 代表 listView 的 一个组,组内的单一数据 代表 列表的 一个 cell;
3、一个 完整的 列表数据,即为 一个 二维数组;即 最外层数组 内每一个元素 均为数组,代表 列表的 一个组数据;而 内层数组 内每一个元素均为 一个单一数据,代表列表中的该组中的一个 cell;
以上 提及的为 列表数据 的创建原则;可参考本人的用法,如下:
1、一个完成的列表数据,其标准形式如下:
@[
@[
object1.bind(@"className").bindHeight(100),
object2.bind(@"className").bindHeight(100),
object3.bind(@"className").bindHeight(100)
],
@[
object4.bind(@"className").bindHeight(100),
object5.bind(@"className").bindHeight(100),
object6.bind(@"className").bindHeight(100)
],
@[
object7.bind(@"className").bindHeight(100),
object8.bind(@"className").bindHeight(100),
object9.bind(@"className").bindHeight(100)
]
];
//其含义为:这个列表有 3个组,每一组有 3 个类名为 className 的cell,且高度为100
2、 可以将 绑定 添加至 数组上,这样就不用每一个数组元素写相同的绑定,如下:
@[
@[object1, object2, object3].bind(@"className").bindHeight(100),
@[object4, object5, object6].bind(@"className").bindHeight(100),
@[object7, object8, object9].bind(@"className").bindHeight(100),
];
//这个写法的含义 与 第1点描述的是一样的
3、针对 列表中 只有一个 组的情况,可省略外层数组,只提供内层数组即可,如:
@[
object1,
object2,
object3
].bind(@"className").bindHeight(100);
//其含义为:这个列表有 1个组,这个组有 3 个类名为 className 的cell,且高度为100
实践证明,这种写法极其便利,而且出现频率极高,能满足绝大部分傻瓜式滚动列表。
以上仅为 列表 cell 的数据组织结构,对于列表中 存在的 组头组尾(UITableViewHeaderFooterView、UICollectionReusableView),其 数据组织 需要与 cell的数据组织 分开处理,便于理解 与 维护。
列表组头组尾数据创建规则:
1、由 多个 单一 数据 构成的 数组对象,即代表 一个 完整列表 的组头/组尾数据;
2、一个 数组 对象 代表 listView 的 一个组头/组尾数据,组内的单一数据 代表 列表的 某一个 组头/组尾;
3、规定 一个二维数组代表 一个listView 的所有组头组尾数据,该二维数组 只包含两个 子数组,超出部分将不作处理;其中,第一个 子数组 代表 组头,第二个 子数组 代表组尾;
4、组头组尾数据并不是 提供多少 就会创建多少,实际数量依赖于 cell 数据。即 有多少组 cell数据,才会相应地 去查找是否有提供的 组头组尾数据;
以上 提及的为 列表组头组尾数据 的创建原则;可参考本人的用法,只是数据的组织形式与cell数据的组织形式略有不同,绑定规则通用,即数组绑定会顺带绑定数组元素,如下:
1、一个 完整的 组头组尾数据,其标准形式如下:
@[
@[
object1.bind(@"headerView").bindHeight(100),
object2.bind(@"headerView").bindHeight(100),
object3.bind(@"headerView").bindHeight(100),
],
@[
object4.bind(@"footerView").bindHeight(100),
object5.bind(@"footerView").bindHeight(100),
object6.bind(@"footerView").bindHeight(100),
]
];
//其含义为:有 3 个 组头数据,创建出来的是 类名为 headerView 高度为 100 的组头View;有 3 个 组尾数据,创建出来的是 类名为 footerView 高度为 100 的组尾View;
注意:实际是否生成 对应 该组头组尾数据的View,还是需要有 cell数据的结构决定。
简便写法也是支持的,如:
@[
@[object1, object2, object3].bind(@"headerView").bindHeight(100),
@[object4, object5, object6].bind(@"footerView").bindHeight(100)
];
//含义同上
2、对于只需提供组头数据的情况,可省略外层数组,只提供内层数组。如:
@[object1, object2, object3].bind(@"headerView").bindHeight(100);
写法上的注意点:
1、如果是 以标准的 二维数组 组织数据,请不要将绑定直接添加至 最外层数组,因为 数组 绑定 时只会传递 一层 数组元素,而不会向下 传递至 数组元素的数组元素,如以下写法 是错误的:
@[
@[
object1,
object2,
object3
]
].bind(@"className").bindHeight(100);
//错误写法,这样绑定只会将绑定传递至 内层数组,而想要绑定的 object1、object2、object3 实质上并没有绑定。
正确的写法为:
@[
@[
object1,
object2,
object3
].bind(@"className").bindHeight(100)
];
// 若存在多组,也是直接绑定内层数组,就能连带 单一数据 的绑定;单组的情况下 可以直接以 一维数组 代替 二维数组;
2、如果存在 需要 一个 数据 运用到 多个 cell 的情况,请使用 mt_reuse(object) 对数据进行包装,如以下写法是错误的:
@[
object.bind(@"className1"),
object.bind(@"className2"),
].bindHeight(100);
// 这样绑定的话,后面的 className2 会把 前面的 className1 覆盖掉,最后生成的为 2 个 className2 的 cell
正确的写法为:
@[
mt_reuse(object).bind(@"className1"),
mt_reuse(object).bind(@"className2"),
].bindHeight(100);
// 使用 mt_reuse() 包装数据后,实质上是将绑定添加至这个 包装之后的 对象 上,由于是不同的 包装对象,所以绑定 能不受影响,传递数据 时 会将这个 重用对象 里的 object 取出 传递至对应的 cell
3、原则上 .bindSize 只会对 UICollectionViewCell 起作用,而 .bindHeight 则对UICollectionViewCell 和 UITableViewCell 都起作用。如以下写法是不起作用的:
object.bind(@"UITableViewCell").bindSize(CGSizeMake(100, 100));
// 对一个 UITableViewCell 绑定了 size 是无效的;因为 UITableViewCell 不需要宽度
正确写法如下:
object.bind(@"UICollectionViewCell").bindSize(CGSizeMake(100, 100));
//或者
object.bind(@"UITableViewCell").bindHeight(100);
object.bind(@"UICollectionViewCell").bindHeight(100);
// UITableViewCell 只对 高度绑定 生效,UICollectionViewCell 则两者都可,优先选择 bindSize,其次再会 选择 bindHeight;如果选择的是 bindHeight, 则它的宽度 等于 父控件 UICollectionView 的宽度
写法上的技巧:
1、绑定添加在数组上时,仍然可以为 单数据添加绑定,若如此做,数组上添加的绑定对于该单一数据将不起作用;这样做的目的是能够 根据实际情况 自行定制数据。
以下以单组数据举例说明,多组写法只是增加单组数量,在此不阐述。
示例1:
@[
object1,
object2.bind(@"className2")
].bind(@"className1").bindHeight(100);
// 为数组的 所有数据 绑定 100 的高度, 类名为 className1 的cell;其中, object2 的 cell 类名为 className2。
//或者
@[
object1,
object2..bindHeight(200)
].bind(@"className").bindHeight(100);
// 为数组的 所有数据绑定类名为 className 的cell,高度为 100;其中, object2 的高度为200。
2、对于变量表示的数组对象,可通过引用数组元素的方式自行定制。如下:
NSArray*array=@[object1, object2];
array.bind(@"className1").bindHeight(100);
array[1].bind(@"className2");
//或者
array.bind(@"className").bindHeight(100);
array[1].bindHeight(200);
这个写法含义同上。
3、对于不需要传入数据对象的情况,可以直接传类名字符串,绑定尺寸。如下:
@"className".bindHeight(100);
@"className".bindSize(CGSizeMake(100,100));
//这个写法 可省略 .bind(@"className")
4、对于只需要进行占位,而不需要传入实质数据的情况,第 3 点已经可以满足,但是对于不同尺寸的情况,前面的写法注意已经提过,是会出现对于同一对象后面的绑定会覆盖前面的情况,所以使用 mt_reuse(object) 进行表面的数据包装,如果需要包装的数据为空,可使用 mt_empty() 代替;即 mt_empty() == mt_reuse(nil)。这样做是为了带来写法上的便利性。针对 第3点 的情况,对于不需要传入实质数据,而又想生成不同大小的 cell,可以这样写:
mt_empty().bind(@"className").bindHeight(100);
mt_empty().bind(@"className").bindHeight(200);
mt_empty().bind(@"className").bindHeight(300);
//或者
mt_reuse(@"className").bindHeight(100);
mt_reuse(@"className").bindHeight(200);
mt_reuse(@"className").bindHeight(300);
mt_empty().bind(@"className").bindSize(CGSizeMake(100,100));
mt_empty().bind(@"className").bindSize(CGSizeMake(200,200));
mt_empty().bind(@"className").bindSize(CGSizeMake(300,300));
//或者
mt_reuse(@"className").bindSize(CGSizeMake(100,100));
mt_reuse(@"className").bindSize(CGSizeMake(200,200));
mt_reuse(@"className").bindSize(CGSizeMake(300,300));
也可缺省绑定 className,代表生成一个默认的空cell。如下:
mt_empty().bindHeight(100);
mt_empty().bindHeight(200);
mt_empty().bindHeight(300);
mt_empty().bindSize(CGSizeMake(100,100))
mt_empty().bindSize(CGSizeMake(200,200))
mt_empty().bindSize(CGSizeMake(300,300))
5、对于 单一数据 要生成多个 相同列表 cell 的情况,可使用 .bindCount(n) 生成包含 n 个 包装该 单一数据的数组,用于快速生成多数据;以下举例说明 生成一个包含多个 使用 同一数据 样式相同cell 列表在 使用 .bindCount(n) 与不使用的区别:
// 不使用
@[
mt_reuse(object),
mt_reuse(object),
mt_reuse(object)
].bind(@"className").bindHeight(100);
//使用
object.bindCount(3).bind(@"className").bindHeight(100);
这个用法比较常用的场景为在开发时造假数据调试用;也可通过该方式造出数组对象,在根据要点2 提及的,自行定制数据。
基础用法总结:
1、 .bind() 用于绑定类名,如果 单一数据 为字符串,则该字符串同时 包含类名绑定的含义,即:
@"className".bindHeight(100);
与
mt_empty().bind(@"className").bindHeight(100);
mt_reuse(@"className").bindHeight(100);
等价。只是写法上不一样,如果存在多个 cell 需要使用相同 的数据,要使用 mt_reuse() 包装。
2、绑定尺寸 常用有两种,bindHeight()、bindSize();其中 UITableViewCell 只会 接收 bindHeight();
3、mt_reuse() 可用于对多个 cell 传递同一个数据的情况,避免 绑定被覆盖;
4、mt_empty() 为 mt_reuse() 包装的数据为空的情况,即 mt_empty() == mt_reuse(nil);多用于做占位cell使用;
5、bindCount(n) 可用于生成 一个包含 n 个包装了相同数据 数组元素的数组对象;多用于造假数据;
6、bindSpacing() 用于绑定在组头组尾数组上,代表 collectionView 几个关键组属性(最小行间距、最小列间距,组间距),用在 列表数据上是不起作用的。
7、二维数组 代表 一个列表数据,里面每一个一维数组代表列表的一个组,组内每一个元素代表列表中 这个组的每一个cell。如果 列表只有一个组,可以简写成一位数组的写法,数组的所有数组元素则代表列表的所有cell。
8、二维数组 代表一个 组头组尾数据,第一个子一维数组里存放组头数据,第二个子一位数组存放组尾数据。如果只提供一个一维数组,默认代表一个组头数据。
总之,用法都很灵活,组织思路简单,多敲代码试验一下就能快速上手了!之后所有关于列表的交互或者样式设置基本都是围绕这一套机制实现。
在 cell、headerFooterView、reusableView 中接收传递的数据:
在编写 这些列表单元格控件时,请继承使用 MTDelegateCollectionViewCell、MTDelegateTableViewCell、MTDelegateCollectionReusableView 和 MTDelegateHeaderFooterView,这几个基类并没有做特殊操作,只是添加了一些 用于辨别 索引的子属性,都是直接继承 对应的 UIKit 控件,这样做能避免直接对原生框架的 UIKit 控件作修改,避免加重原生框架负担,同时便于框架内部维护。
以上 4个控件实现了一个很重要的协议:MTExchangeDataProtocol,协议如下:
@protocol MTExchangeDataProtocol
/**当接收到数据*/
-(void)whenGetResponseObject:(NSObject*)object;
/**接收数据的类型*/
-(Class)classOfResponseObject;
@optional
以下以 UICollectionViewCell 的自定义,解说如何从以上的便捷写法中,获取到这个 单一数据。
1、继承 MTDelegateCollectionViewCell,新建类 DemoCell。如下:
@interface DemoCell : MTDelegateCollectionViewCell @end
2、由于基类已经实现了 MTExchangeDataProtocol 协议,这里只需重写两个协议方法即可。如下:
@implementation DemoCell
// 数据 最后会通过 whenGetResponseObject: 方法传递进来
-(void)whenGetResponseObject:(NSDictionary *)object
{
NSLog(@"%@", object);
}
// 这个方法会判断 数据是否为想要的类型,这里需要的数据类型为 NSDictionary,所以
whenGetResponseObject 方法接收到数据时可以放心使用, 保证了数据类型的准确性
-(Class)classOfResponseObject
{
return [NSDictionary class];
}
@end
3、最后 组织后数据传入即可,如下:
@{}.bind(@"DemoCell").bindHeight(100);
这样就能把这个空字典 传递进 DemoCell 了。
这里只是举例,实际上传什么数据,按实际情况而定,默认基类 classOfResponseObject 没有限制类型,可传递任意基类为NSObject的对象。
实际使用中,可以构造模型数据的字典对象,机制内部默认会将字典转换为 classOfResponseObject 对应的模型对象。如 cell 需要一个 AModel 如下:
@interface AModel
@property (nonatomic, strong) NSString* a;
@property (nonatomic, strong) NSString* b;
@end
则我们在构造数据时,可以给定一个字典对象,如下:
@{
@"a" : @"a",
@"b" : @"b"
}.bind(@"className")
listView 代理数据源设置:
先来 看看 listView 快速创建数据源的几个方法:
1、添加代理数据源:
[self addTarget:delegate EmptyData:emptyData DataList: dataList SectionList:sectionList];
delegate:为一个遵循 UITableViewDelegate、UICollectionViewDelegateFlowLayout、MTDelegateProtocol 的代理对象;该代理对象 也会传递至 cell 的 mt_delegate 属性中,用于处理 cell 传递过来的交互操作,通常是一个 ViewController;
MTDelegateProtocol 协议 如下:
@protocol MTDelegateProtocol <NSObject>
@optional
-(void)doSomeThingForMe:(id)obj withOrder:(NSString*)order;
-(id)getSomeThingForMe:(id)obj withOrder:(NSString*)order;
-(void)doSomeThingForMe:(id)obj withOrder:(NSString*)order withItem:(id)item;
-(id)getSomeThingForMe:(id)obj withOrder:(NSString*)order withItem:(id)item;
@end
代理对象通过实现这些可选方法 实现 cell 与 外界的数据交互,order 通常作为一个事件辨识,用于区分 交互事件,具体用途按自己情况而定,这个协议的主要目的就是用来做 交互的。
对于单纯的 cell 点击事件,完全可以在代理里实现 tableView:didSelectRowAtIndexPath: 或
collectionView:didSelectItemAtIndexPath: 进行处理;同理,UIScrollView的代理方法也写在代理中即可,这套机制内部会将这些代理方法带出来到我们传递的这个代理对象中进行处理,只是将数据源方法包装起来在内部实现,省却我们重复编写数据源代理方法的麻烦。
dataList:即是上面我提到的列表数据的构造,就是扔到这里使用的。
sectionList:同理,即是上面提到的组头组尾数据构造,在这里做为参数传递。
emptyData:当上面的 dataList 单一数据数量为 0 时,会调用这个 单一数据创建空数据页面,原则上可以不传尺寸,内部会自动生成同 listView 同等宽高的一个cell。
上面所说的这个 addTarget:EmptyData:DataList:SectionList: 方法做了这几个操作:
1、给定列表数据;
2、给定组头组尾数据;
3、给定一个空数据;
4、为 listView 添加一个代理对象,用于作数据传递;
在实际使用情况中,用的比较多的通常是这个方法的一个便利方法:
- (void)addTarget:(id)target;
这个方法的作用同上,只是传递的 dataList、sectionList、emptyData 为空,而转而通过listView 的刷新方法去进行传递。
2、listView 刷新数据源 方法如下:
- (void)reloadDataWithDataList:(NSArray*)dataList;
- (void)reloadDataWithDataList:(NSArray*)dataList EmptyData:(NSObject*)emptyData;
- (void)reloadDataWithDataList:(NSArray*)dataList SectionList:(NSArray*)sectionList;
- (void)reloadDataWithDataList:(NSArray*)dataList SectionList:(NSArray*)sectionList EmptyData:(NSObject*)emptyData;
以上刷新方法的参数含义同上,只是略有不同,缺省不传的参数,缺省参数默认传 nil;这些便利方法最终内部都会去调 最后一个方法:
- (void)reloadDataWithDataList:(NSArray*)dataList SectionList:(NSArray*)sectionList EmptyData:(NSObject*)emptyData;
刷新方法 与 addTarget 方法不同的地方在于:
1、交互操作的处理 通过在 addTarget 方法中指定 代理对象,而后由这个代理对象处理这些交互事件即可,所以没必要放在 频繁调用的 reloadData 方法中;
2、刷新方法 默认回去调用 listView 的原生方法 reloadData,用于刷新列表,这个方法是作为刷新存在的。
两个方法相同的地方在于:都留有传递 构造数据 的参数 用于创建 列表cell。
以下提供一个实际使用过程中,listView 搭建的常用套路:
1、首先你得有一个listView;
2、先绑定 target,如下:
[listView addTarget: target];
// 先不用急着提供数据源,因为列表 总会涉及到数据刷新,可考虑直接将数据源放到刷新方法去调用
3、提供数据源,刷新列表,如下:
[listView reloadDataWithDataList: dataList SectionList: sectionList EmptyData: emptyData];
可根据项目的实际情况,调用对应的便利方法即可。
自适应高度的使用:
对于固定尺寸的 cell,用 bindHeight、bindSize 的确已经很方便了,但是对于一些不确定高度的情况,用起来总不那么顺手,因为要先计算出高度,再传入高度,而要计算高度,就需要知道这个 cell 内子控件怎么部署,每一个子控件的间隔、位置和高度,这样无疑是增大工作量,只为计算一个高度,确实不划算。于是,就有了自适应高度这样一个东西。
先来看几个新知识。
1、在基于这一套机制下的单元格 cell 中,遵循名为 MTInitProtocol 的协议,这个协议中有一个可选方法,如下:
/**反向计算父控件高度*/
-(CGSize)layoutSubviewsForWidth:(CGFloat)contentWidth Height:(CGFloat)contentHeight;
其中,contentWidth 和 contentHeight 为 listView 的 宽和高,这个方法是在数据源调用 计算尺寸的代理方法内部被调用,用于 计算 cell 的实时高度,返回值 CGSize 即为 cell 的尺寸。注意,对于 UITableViewCell,实际上 width 是无作用的,因为UIKit 并没有提供 对于的 宽度设置代理方法。
通过这种方式,我们在编写 cell 的时候,就可以在这个方法中 顺带将 cell 在实际展示时,根据子控件的情况而 展示的尺寸 求出来,而不再需要在外部再计算尺寸传入,非常方便。同时,这个方法是可以用来布局子控件的,因为你的子控件布局完成后,基本上 这个 cell 整体宽高就出来了。
当然,使用自适应高度并不是自发的,需要在构造数据中去开启:
2、在构造你的单一数据时,使用 .automaticDimension() 即代表启用自适应高度。你不再需要使用 .bindHeight、.bindSize 去规定 cell 的尺寸,即使你使用了也不会起作用,从优先级来看为:.automaticDimension() > .bindSize > .bindHeight,使用规则同上说的列表数据规则。绑定了 .automaticDimension() 后,机制内部将不会去检查 .bindHeight 或 .bindSize,而会直接去调用 cell 的 layoutSubviewsForWidth:Height: 方法,获取我们已经在cell 内部计算出来的 尺寸。
示例如下:
//不使用自适应高度
object.bind(@"className").bindHeight(100);
object.bind(@"className").bindSize(CGSizeMake(100, 100));
//使用自适应高度
object.bind(@"className").automaticDimension();
// cell内部
-(CGSize)layoutSubviewsForWidth:(CGFloat)contentWidth Height:(CGFloat)contentHeight
{
return CGSizeMake(100, 100);
}
3、先来看一个block:
typedef void(^MTAutomaticDimensionSize)(CGSize size);
通过在构造数据时绑定 .automaticDimensionSize(MTAutomaticDimensionSize),可以在获取到计算高度后,回调出去作处理。如:
object.bind(@"className").automaticDimension().automaticDimensionSize(^(CGSize size){
//..........
});
根据需要决定是否需要回调,实际开发过程中用处比较小。
自适应高度的目的,有三个好处:
1、解决了 不确定高度 带来的繁琐计算问题;
2、将 一个 cell 的样式设置 和 尺寸设置 统一至 cell 内部去实现,而不再需要分到去外部使用代理方法去计算,一来麻烦,二来没必要,尺寸本来就属于cell 的部分,不需要拿出去让外部决定,cell 自己清楚自己内部有什么子控件,这些子控件又是什么内容,完全可以自己确定高度;
3、布局子控件,以往 手动布局 都是 通过原生提供的 layoutSubviews 方法来布局子控件,现在,你可以放心地在这个计算尺寸的方法里布局子控件,继而得出 cell 的尺寸,一举两得。然后再在 layoutSubviews 中,调用 layoutSubviewsForWidth:Height: 方法,完成子控件布局。如下:
-(void)layoutSubviews
{
[super layoutSubviews];
[self layoutSubviewsForWidth:self.width Height:self.height];
}
//虽然机制内部 会调用 layoutSubviewsForWidth: Height: 获得尺寸,但实际上 布局子控件最后还是需要走 layoutSubviews 方法,所以需要在这里再调用一次,将 cell 的宽高传入。
注意,layoutSubviewsForWidth: Height: 中请不要直接引用 使用 cell 的宽高,因为你正在计算 cell 的宽高 是无法获取它的尺寸的,本质上只是将写在 layoutSubviews 的布局逻辑 转移到这个方法上,使得这个方法既能布局子控件,也能计算尺寸。
如果使用 本人封装的 样式设置框架,上面示例在 layoutSubviews 中调用计算高度这一步 是不需要的,在框架基类中已经实现,直接所有自定义cell 重写 layoutSubviewsForWidth: Height: 即可。关与 样式设置 与 这个列表快速搭建框架联用,将在另一章中介绍,本章不作阐述。
总结来说,自适应高度的使用步骤为:
1、先在 自定义的 cell 中重写 layoutSubviewsForWidth:Height: 方法,布局子控件的同时顺带计算高度;示例如下:
-(CGSize)layoutSubviewsForWidth:(CGFloat)contentWidthHeight:(CGFloat)contentHeight
{
self.label.x = 15;
self.label.y = 20;
[self.label sizeToFit];
return CGSizeMake(contentWidth, label.maxY +100);
}
2、在构造数据时,使用 .automaticDimension() 绑定单一数据,开启自适应高度:
object.bind(@"className").automaticDimension();
实际上,不使用自适应高度,也可以重写 layoutSubviewsForWidth:Height: 方法,可以将子控件布局写在里面,在 layoutSubviews 中调用即可,这样无论使用 自适应高度 与否,完全取决于单一数据如何绑定,是使用 .automaticDimension() 还是使用.bindHeight、.bindSize。
点击事件绑定:
除了通过 MTDelegateProtocol 代理模式进行数据交互外,还能通过构造数据时绑定 点击事件回调的方式进行交互,这种方式使用更便捷。
先来看一个 block:
typedef void(^MTClick)(id _Nullable object);
可以在构造数据时,使用 .bindClick(MTClick) 绑定点击回调:
data.bind(@"className").bindClick(^(id object) {
});
.bindClick 会为 数据 通过运行时属性 mt_click 将回调保存下来,当触发了didSelectRowAtIndexPath 或者 didSelectItemAtIndexPath,会将 indexPath 作为参数调起回调。
当然,如果 在代理中实现了 didSelectRowAtIndexPath 或 didSelectItemAtIndexPath 方法,也会触发方法调用。
对于 cell 内子控件(比如按钮) 触发的点击事件,需要自行调起 传入数据的 mt_click(object) 来调起回调,可以通过 为传入的 object 使用 .bindOrder(NSString) 绑定字符串标识区分是何种事件。如在 cell 内由于触发了按钮点击,调起回调如下:
if(self.mt_click) self.mt_click(object.bindOrder(@"ClickEvent"));
要获取绑定的标识,通过属性 .mt_order 获取。如:
data.bind(@"className").bindClick(^(id object) {
if([object.mt_order isEqualToString:@"ClickEvent"]) {//.........}
});
关于 .bindClick 的深度用法,在本人的 样式设置架构 里得到充分利用,上面介绍的绑定标识步骤在框架中已有实现,不需要我们再去手动.bindOrder,只需去写业务回调即可。详情请阅读我的 样式设置架构文章,里面将这种列表搭建方式运用的淋漓尽致。
至此,关于 列表的快速搭建用法已介绍完毕,具体使用,还是需要多实践才能体会到 这套架构的好处。
最后说两句:
1、可查看我的库源码,里面除了这个框架,还有各种我在开发中经历过的,最后封装成库的使用功能,之后我也会针对性的为这些功能写文章,讲解如何使用。
2、因为内容有点多,精力有限,实在不可能每样东西都能兼顾, 深入理解底层原理能帮助你快速上手如何使用。
3、cocoaPod 暂时不会更新,原因 我还在想十字滚动的多重滚动情况怎么写,我们常遇到的情况通常是一层,即上面是页卡可以点击加左右滑,也可一个列表上下滑的功能。可能存在这个功能嵌套的情况,所以我打算完成这个功能再更新 cocoaPod。这套机制已经很稳定了,不经常改动,已稳定经历 3 个 app的开发,可放心使用。
源码地址: https://github.com/alrjqow/MagicThought.git,
理解这套机制,可参考这些库模块内容:
MTDelegateMode 关键文件:
1、MTDataSource.m,这个文件主要讲 数据源的实现都放在这里;
2、UIView+Delegate,通过这个分类文件,暴露调用方法;
Dependency 关键文件:
NSObject+ReuseIdentifier,通过这个分类文件,实现单一数据的绑定功能;