列举简单实例,学习封装
-
简单的tableView的实现。
- 数据模型
- 组模型
- 行模型数组(对应的是每一行cell的值)
- 头部标题
- 底部标题
- 行模型 (对应cell)(与cell的内部的属性相对应)
- 文字属性
- 图片属性
- 组模型
- cell控件
- 数据源方法
- 组数
- 行数
- 每行的cell值
- 每组的头标题
- 每组的底标题
- 代理方法
- 点击了cell实现某种方法
- 数据模型
-
tableView是一个最能体现MVC思想的控制器之一,控制器提供数据,数据转换模型,模型修改控件属性,控制器刷新控件,让控件显示模型更新数据。(话语实现就是这样,但真的做的时候,需要一步步思考。)
- [编程思路,由大到小]
- [实现思路,由小到大]
- [封装思路,方便使用,管理,由外及内]。
-
对于tableView的实现。
- 添加数据(用什么添加,那什么接收)
- 实现数据源方法
-
添加数据(用模型添加,拿数组接收)
-
接收数组(添加的时候才使用,所以想到懒加载)
// 类扩展中添加属性(因为无需与外界就行传递,交互,所以写在类扩展中最好) @property (nonatomic,strong) NSMutableArray *groups // 懒加载 - (NSMutableArray *)groups { if(!_groups) { _groups = [NSMutableArray Array]; } return _groups; }
-
添加组模型数据
- 组模型数据要加到数组中
[self.groups addObject: group];
- 组模型是有行模型数组和头尾标题组成的,所以就有了组模型的模型搭建
- 组模型数据要加到数组中
-
//模型的所有属性,几乎都是用来让外界使用的,所以属性都写在外边即可。并提供类方法,方便外界创建模型(所谓模型,就是把数据整合化).h文件中的
// 头部标题
@property (nonatomic,copy) NSString *headerTitle;
// 尾部标题
@property (nonatomic,copy) NSString *footerTitle;
// 行模型数组
@property (nonatomic,strong) NSArray *items;
// 构造方法(以类名开头)(简单的不许要传参数, 复杂的传参数,但是无非就是把传入的参数,给自己的属性进行赋值)
+ (instancetype)group;
// .m中的实现方法
+ (instancetype)group
{
LXLGroupItem *group = [[self alloc]init];
return group;
}
/* 这里解释两个事情
1.为什么用instancetype,而不用id。非常实用的一点是instancetype代指的是:实例对象,id代指的是:任何对象,有时候简单数据变量都可以。 这就局限了id是不能实现点语法,而instancetype是可以实现的。
2. 为什么用self,而不用当前类(LXLGroupItem),这是为了继承,以及程序扩展性才这么写的,以后也必须这么写。如果自己有子类,那么子类就会继承自己的方法,但是如果写成了LXLGroupItem,就一直创建的是父类对象,而不是子类对象。(有些需求是子类独有的,所以必须创建子类对象) (这是继承中的内容)。
*/
- 怎么方便使用,
LXLGroupItem *group = [LXLGroup group];
group.headerTitle = @"..."
group.footerTitle = @"..."
group.items = @[ , , ,...];
- 行模型创建 (模仿组模型进行创建)(模型都继承自NSObject,因为其内部就是一群数据。个人觉得就是数据的归类)
- 具有的属性 (根据cell而定)
- 提供构造方法
-
实现数据源方法
- 有多少组
self.groups.count
- 每一组对应的行数
- 取出组模型
LXLGroupItem *group = self.groups[section];
- 对应的行数
group.item.count
- 取出组模型
- 每行显示的数据
- 取出组模型
LXLGroupItem *group = self.groups[indexPath.section];
- 取出行模型
LXLItem *item = group.item[indexPath.row];
- 模型属性进行赋值操作。
cell.... = item...
- .....
- 取出组模型
- 有多少组
-
思考
- 一般我们实现最简单的数据源方法的确是这样,但是往往我们常常会有其他的考虑,比如缓存池的情况,比如cell的Accessory的标志,以及cell类型等等的考虑。
- 我们不应该让外界知道我们有如此的设计,让程序分工明确,所以这些独属于cell的设置,由cell内部进行考虑。
-
外界如何使用:
- 创建cell,
- 给cell赋值
- 返回cell
-
创建cell ,
- 对于tableView来说,cell先从缓存池中读取,在进行判断,如果没有的话则进行创建,创建过程中需要设置其类型
- 传入参数应该有缓存池的tableView,以及所要的类型
-
给cell赋值
- cell赋值最简单的方式则为 ,cell.item = item;直接接收模型属性,在其内部一一赋值。
- 对于accessoryView的选择应该也由模型决定
-
两种方案
- 模型属性中添加两个属性,用来决定accessoryView。
- 给模型加子类,由类名来区别accessoryView。
-
选择第二种方案,应为更符合封装思想,不同的类管理不同的模型。(创建模型时,用不同类进行创建)
-
-
代码实现
// 构造方法,创建cell
+ (instancetype)cellWithTableView:(UITableView *)tableView withStyle:(UITableViewCellStyle )style
{
static NSString *ID = @"setting";
LXLSettingCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
cell = [[self alloc]initWithStyle:style reuseIdentifier:ID];
}
return cell;
}
// 外界接收到item,拿来一一赋值
- (void)setItem:(LXLSettingItem *)item
{
_item = item;
[self setUp];
[self setUpAccessoryView];
}
- (void)setUp
{
self.imageView.image = _item.image;
self.textLabel.text = _item.title;
self.detailTextLabel.text = _item.subTitle;
}
// 根据接收过来的item的类,进行选择。
- (void)setUpAccessoryView
{
// 先if 再 else if 再 else 按顺序来,有个良好的习惯
if ([_item isKindOfClass:[LXLSettingItemArrow class]]) {
UIImageView *imageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"arrow_right"]];
self.accessoryView = imageView;
}else if ([_item isKindOfClass:[LXLSettingItemSwitch class]]) {
UISwitch *switchView = [[UISwitch alloc]init];
self.accessoryView = switchView;
}else{
self.accessoryView = nil;
}
}
-
关于block的强大使用。
- 封装代码块,可以传入参数。
- 把事件包装起来,并传入参数。
-
思考
- 对于tableView的实现,那么它的cell 应该有点击跳转到某个控制器的功能。(每个cell对应不同的控制器,cell决定控制器,而模型决定cell,所以模型决定控制器跳转。)对于要跳转的控制器应该由它cell的模型属性进行决定。
- 两种方案进行选择:
- 模型加入属性:
-
直接加入控制器
-
@property (nonatomic,strong) UIViewController *vc
[控制器必须强引用] -
加一个属性显示控制器的类名
-
`@property(nonatomic,assign) Class vc;
-
另一种方式用
block
封装代码块,来执行事件,
-
- 模型加入属性:
- 注意
block
定义式 - void(^方法名)(参数类型+参数名)
@property (nonatomic,strong) void(^block)();
- block的定义,只是定义了里面的事件,但并没有去实现
定义: void(^block)(int a , int b) = ^(int a, int b){
NSLog(@“%d”,a+b);
} - 只有调用了block( ),才能进行block的实现
实现: block(10,15); 他就会执行这个block, 打印25.
-
所以对于一些事件可以封装到block中
- 此处先进行跳转的封装说明吧
// block 写法为 ^ + ( 参数 类名+参数名) {要完成的事,一般传入事件} ,但要注意循环引用 有self,或者成员属性,一定要将self 变为弱指针 __weak typeof(self) weakSelf = self; item.block = ^(NSIndexPath *path){ LXLRedeemController *vc = [[LXLRedeemController alloc]init];
vc.navigationItem.title = @"提醒和推送";
[weakSelf.navigationController pushViewController:vc animated:YES];
}; - 当点击此cell的时候,进行判断,看是否有block的存在
如果有则执行block - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ [tableView deselectRowAtIndexPath:indexPath animated:YES]; LXLGroupItem *group = self.groups[indexPath.section];
LXLSettingItem *item = group.items[indexPath.row];
if (item.block) {
// 传入参数确定是哪一行的cell被点击了
item.block(indexPath);
}
} - 此处先进行跳转的封装说明吧
-
对于添加类名属性来进行跳转的代码实现
-
item. vc = [UITableViewController Class];
-
点击cell时 同样进行判断 此属性是否有值
如果有,则创建对应的控制器,进行跳转。
需要注意的是:此类跳转只针对与 cell的accessoryView 是箭头的操作,如果是switch的话,则跳出蒙版。
- 所以vc属性只能针对与箭头模型。 如果点击了cell,则取出模型,判断其类型,然后在执行跳转还是弹出蒙版。
-
对于最后的结论,用block最好
- 无论 弹出蒙版,还是跳转页面,总之都是进行了一定的事件。将其事件交给block,当点击cell 执行事件即可, 无需判断类型。
- 总之,对于点击cell处理的事件以后都交给block进行管理最好了 -
对于cell点击弹出键盘事件
-
首先应该应该想到
- 点击什么样的cell弹出键盘,
- 键盘怎么才能弹出。
- 键盘是由哪行cell引出的
- 什么时候退出键盘
item.block = ^(NSIndexPath *path){ // 只要确定选中的行,就能够让键盘弹出后,不挡住选中的行 UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:path]; UITextField *textField = [[UITextField alloc]init]; [textField becomeFirstResponder]; [cell addSubview:textField];
};
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ [tableView deselectRowAtIndexPath:indexPath animated:YES];
LXLGroupItem *group = self.groups[indexPath.section];
LXLSettingItem *item = group.items[indexPath.row];
if (item.block) {
item.block(indexPath);
}
}
- 退出键盘
滚动时退出键盘
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ [self.view endEditing:YES];
}