事先已准备好plist文件,结构如下
1.Root为Array。
2.里面有十一个字典(每个字典为一个分组)
3.每个分组里有friends,name,online三个属性
4.friends属性里有若干字典(每个字典代表一个好友)
5.每个好友字典里有4个属性,name,icon,intro和vip
6.需要两个模型,分组模型和其里面的好友模型
一、加载模型数据
1.新建一个好友模型MJFriend,添加4个属性,提供加载模型数据的方法并实现
@property(nonatomic,copy)NSString *name; @property(nonatomic,copy)NSString *icon; @property(nonatomic,copy)NSString *intro; @property(nonatomic,assign,getter = isVip)B0OL vip; +(instancetype)friendWithDict:(NSDictionary *)dict { return [self alloc] initWithDict:dict]; } -(instancetype)initWithDict:(NSDictionary *)dict { if(self = [super init]){ //好友字典里所有的键值对都赋值给friend的4个属性了 [self setValueForKeysWithDictionary:dict]; //KVC } return self; }
2.新建一个好友分组模型MJFriendGroup,添加3个属性,提供加载模型数据的方法并实现
@property(nonatomic,copy)NSString *name; @property(nonatomic,strong)NSArray *friends;//装的都是MJFriend模型 @property(nonatomic,assign)int online; +(instancetype)groupWithDict:(NSDictionary *)dict { return [self alloc] initWithDict:dict]; } -(instancetype)initWithDict:(NSDictionary *)dict { if (self == [super init]){ //1.注入所有属性(但是目前friends属性里装的是字典) [self setValueForKeysWithDictionary:dict]; //KVC //2.特殊处理friends属性(将friends字典转成MJFriend模型) NSMutableArray *friendArray = [NSMutableArray array]; for (NSDictionary *dict in self.friends){ MJFriend *friend = [MJFriend friendWithDict:dict]; [friendArray addObject:friend]; } self.friends = friendArray; } return self; }
3.控制器拿到groups数组属性并懒加载(在其中将分组字典转为模型)
@property(nonatomic,strong)NSArray *groups; //安全起见用Array类型 -(NSArray *)groups { if(_groups == nil) { NSArray *dictArray = [NSArray arrayWithContentsOfFile:[NSBundle mainBundle] pathForResource:@"" ofType:nil]; NSMutableArray *groupArray = [NSMutableArray array]; for(NSDictionary *dict in dictArray){ MJFriendGroup *group = [MJFriendGroup groupWithDict:dict]; [groupArray addObject:group]; } _groups = groupArray; } return _groups; }
PS:groupArray数组里面装的就是group模型。group模型里面的friendsArray数组装
的是friend模型。
二、显示模型数据
0.将默认控制器MJViewController改为继承自UITableViewController。将storyboard里的默认View删除,拖一个TableViewController,Class设置为MJViewController ,如果不设置Class,那么就不会创建MJViewController,MJViewController里的代码,相当于白写。
1.设置数据源,遵守数据源协议,并实现数据源方法
#pragma mark - 数据源方法 第一个数据源方法:numberOfSectionsInTableView:方法 return self.groups.count; 第二个数据源方法:numberOfRowsInSection:方法 MJFriendGroup *group = self.groups[section]; return group.friends.count; 第三个数据源方法:numberOfRowsInSection:方法 { //1.创建cell MJFriendCell *cell = [MJFriend cellWithTableView:tableView]; //2.设置cell的数据 MJFriendGroup *group = self.groups[indexPath.section]; MJFriend *friend = group.friends[indexPath.row]; cell.friendData = friend; return cell; }
三、添加头部控件(头部控件也有循环利用机制)
1.设置代理,遵守代理协议,并实现代理方法
//内部会设置位置和尺寸,不用自己设置 -(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection :(NSInteger)section { //1.创建头部控件 MJHeaderView *header = [MJHeaderView headerViewWithTableView: tableView]; header.delegate = self; //2.给header设置数据(给header传递模型)[重写完setGroup方法后进行] header.group = self.groups[section]; return header; }
2.
1>自定义一个MJHeaderView,并提供一个类方法快速创建一个HeaderView
+(instancetype)headerViewWithTableView:(UITableView *)tableView { static NSString *ID = @"header"; MJHeaderView *header = [tableView dequeueReusableHeaderFooterViewWithIdentifier:ID]; if (header == nil){ header = [MJHeaderView alloc] initWithRreuseIdentifier:ID]; } return header; }
2>重写initWithReuseIdentifier:方法
//在这个初始化方法中,MJHeaderView的frame/bounds没有值 { if(self = [super initWithReuseIdentifier:reuseIdentifier]){ //添加子控件 //1.添加按钮 UIButton *nameView = [UIButton buttonWithType:UIButtonTypeCustom]; //设置按钮的背景图片 [nameView setBackgroundImage:[UIImage imageNamed: @"buddy_header_bg"] forState:UIControlStateNormal]; [nameView setBackgroundImage:[UIImage imageNamed: @"buddy_header_bg_highlighted"] forState:UIControlStateHighlighted]; //设置按钮内部左边的箭头图片 [nameView setImage:[UIImage imageNamed:@"buddy_header_arrow"] forState:UIControlStateNormal]; [nameView setTitleColor[UIColor blackColor] forState:UIControlStateNormal]; //设置按钮的内容左对齐 nameView.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; //设置按钮的内边距 nameView.contentEdgeInsets = UIEdgeInsetsMake(0,10,0,0); //设置标题的内边距 nameView.titleEdgeInsets = UIEdgeInsetsMake(0,10,0,0); [nameView addTarget:self acton:@selector(nameViewClick) forControlEvents:UIControlEventTouchUpInside]; //设置按钮内部的imageView的内容模式为居中(不要拉伸) nameView.imageView.contentMode = UIViewContentModeCenter; //超出边框的内容不需要裁剪 nameView.imageView.clipsToBounds = NO; [self.contentView addSubview:nameView]; self.nameView = nameView; //声明成员属性后一定要赋值 //2.添加好友数 UILabel *countView = [[UILabel alloc] init]; countView.textAligment = NSTextAligmentRight; countView.textColor = [UIColor grayColor]; [self.contentView addSubview:countView]; self.countView = countView; //声明成员属性后一定要赋值 } return self; }
经验:某个控件出不来
1.frame的尺寸和位置对不对(不要在初始化方法中设置frame)
2.hidden是否为YES
3.有没有添加到父控件中
4.alpha是否小于0.01
5.被其他控件挡住了
6.查看父控件的前面5个情况
PS:因为layoutSubviews方法里要用到nameView和countView,所以将它们添加为属性
@property(nonatomic,weak) UILabel *countView; @property(nonatomic,weak) UIButton *nameView; //一般在这里面布局内部的子控件(设置子控件的frame) //当一个控件的frame发生改变的时候就会调用 -(void)layoutSubviews { #warning 一定要调用父类的方法 [super layoutSubviews]; //1.设置按钮的frame self.nameView.frame = self.bounds; //2.设置好友数的frame CGFloat countY = 0; CGFloat countH = self.frame.size.height; CGFloat countW = 150; CGFloat countX = self.frame.size.width - 10 - countW; self.countView.frame = CGRectMake(countX,countY,countW,countH); }
//如果头部控件每一组高度一样,那么就用下面的方法设置高度
self.tableView.sectionHeaderHeight = 44;
//如果头部控件每一组高度不一样,那么就用下面的方法设置高度
//heightForHeaderInSection:
四、设置头部数据
1.在HeaderView中添加一个group模型属性
@property(nonatomic,strong)MJFriendGroup *group;
2.重写setter方法
-(void)setGroup:(MJFriendGroup *)group { _group = group; // 1.设置按钮文字(组名) [self.nameView setTitle:group.name forState:UIControlStateNormal]; //2.设置好友数(在线数/总数) self.countView.text = [NSString stringWithFormat:@"%d/% d",group.online,group.frinedns.count]; }
3.将创建cell和设置cell数据的代码封装起来
1>新建一个Cell
2>提供一个类方法,传TableView返回一个cell,一个Cell对应一个Friend模型
+(instancetype)cellWithTableView:(UITableView *)tableView; //friend是C++的关键字,不能用friend作为属性名 @property(nonatomic,strong)MJFriend *friendData;
3>重写setter方法(在这里给cell控件赋值)
-(void)setFriendData:(MJFriend *)friendData { _friendData = friendData; self.imageView.image = [UIImage imageNamed:friendData]; self.textLabel.text = friendData.name; self.textLabel.textColor = friendData.isVip ? [UIColor redColor] : [UIColor blackColor]; self.detailTextLabel.text = friendData.intro; }
4>实现cellWithTableView:方法
{ static NSString *ID = @"friend"; MJFirendCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; if (cell == nil){ cell = [MJFirendCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID]; } return cell; }
五、展开/合并好友组
1.用addTarget:方法监听按钮点击
2.来到MJFriendGroup.h,添加一个BOOL属性(标识这组是否需要展开)
@property(nonatomic,assign,getter = isOpened) BOOL opened;
3.重写第二个数据源方法 numberOfRowsInSection:
{ MJFriendGroup *group = self.groups[section]; return (group.isOpened ? group.friends.count : 0); }
4.实现按钮的方法
- (void)nameViewClick { //1.修改模型的标识(状态取反) self.group.opened = !self.group.isOpened; //2.刷新表格 if([self.delegate respondsToSelector:@selector (headerViewDidClickNameView:)]) { [self.delegate headerViewDidClickNameView:self]; }
5.来到MJHeaderView.h新建代理协议,添加代理属性(因为HeaderView想把它内部的点击告诉控制器) ,实现代理方法
@protocol MJHeaderViewDelegate(NSObject) @optional -(void)headerViewDidClickNameView:(MJHeaderView *)headerView; @property(nonatomic,weak)id<MJHeaderViewDelegate> delegate;
#pragma mark - headerVIew的代理方法 //点击了headerView上的分组名按钮时就会调用 -(void)headerViewDidClickNameView:(MJHeaderView *)headerView { [self.tableView reloadData]; }
说明:因为只有控制器才有reloadData方法,所以headerVIew不能直接使用,只能委托控制器这个"代理"去刷新表格。
6.刷新表格是不会从缓存池里取的,会重新创建。所以拿到重新创建好的新View来修改
//当一个控件被添加到父控件中就会调用(系统自动调用) -(void)didMoveToSuperview { //控制箭头的状态 if [self.group.opened] { self.nameView.imageView.transform = CGAffineTransformMakeRotation (M_PI_2); } else { self.nameView.imageView.transform = CGAffineTransformMakeRotation(0); } }