概述:本文是黑马程序员UI视频教程第九天,QQ好友列表案例的总结
该案例中主要涉及的知识点有:改变图片在UIButton中的平铺方式、UITableViewHeaderFooterView、设置按钮中图片和文本的内边距和对齐方式、tableView中按组进行重载、自定义delegate协议、layoutSubViews方法、巧妙传递用户的单击信息等内容
基本思路:
- 使用UITableviewController,拷贝素材、创建界面、字典转模型,懒加载
- 实现tableView的数据源方法
- 设置headerView
- 实现点击headerView箭头旋转效果
- 实现点击headerView好友列表展开效果
- 实现VIP会员名称显示为红色
最终效果如下:
1、拷贝素材、创建界面、字典转模型及懒加载
- 该案例中仅包含一个tableView,因此使用一个tableViewController开始将更方便。删除原来的viewController,创建新的tableViewController,然后使其成为初始控制器initial View。创建基于UITableViewController的子类,并将其挂在创建的tableViewController下。
- 注意该plist文件为字典嵌套字典的结构,因此字典转模型也需要分两层来写
2、实现TableView的数据源方法
指定tableView的Section,
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
指定tableView每个Section的行数,
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
-
指定tableView每行所使用的Cell,
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
从效果上看单元格的形式与subtitle形式的单元格基本一致,可以在代理方法内部使用标准subtitle形式的单元格并为内部控件赋值。但由于我们还要进行更多细节的调整,将单元格单独实现更有利于后续的改进,代码的可读性高。
因为每个单元格的形式基本一致,可以考虑使用xib文件来创建单元格
- 创建xib单元格
- 创建基于UITableViewCell的子类,并与xib文件进行挂接。该子类中应包含LJFriend成员(或者将LJFriend对象作为参数传递给创建单元格的类方法)
- 实现创建单元格的类方法,重写LJFriend成员的set方法。在set方法中根据是否是vip设置昵称的字体颜色是否为红色
#import
#import "LJFriend.h" @interface LJFriendCell : UITableViewCell @property(nonatomic , strong) LJFriend * friendCell_model; +(instancetype)friendCellWithTableView:(UITableView *)tableView; @end #import "LJFriendCell.h" @interface LJFriendCell () @property (weak, nonatomic) IBOutlet UILabel *lbl_cell_title; @property (weak, nonatomic) IBOutlet UILabel *lbl_cell_subtitle; @property (weak, nonatomic) IBOutlet UIImageView *img_cell_icon; @end @implementation LJFriendCell #pragma mark - 封装创建单元格的类方法 +(instancetype)friendCellWithTableView:(UITableView *)tableView { static NSString * reuseID = @"friendCell"; LJFriendCell * cell = [tableView dequeueReusableCellWithIdentifier:reuseID]; if (cell == nil) { cell = [[[NSBundle mainBundle] loadNibNamed:@"LJFriendCell" owner:nil options:nil] lastObject]; } return cell; } #pragma mark - 重写LJFriend成员的set方法 -(void)setFriendCell_model:(LJFriend *)friendCell_model { _friendCell_model = friendCell_model; //设置头像 self.img_cell_icon.image = [UIImage imageNamed:friendCell_model.icon]; //设置昵称 self.lbl_cell_title.text = friendCell_model.name; self.lbl_cell_title.textColor = friendCell_model.isVip ? [UIColor redColor]:[UIColor blackColor]; //设置签名 self.lbl_cell_subtitle.text = friendCell_model.intro; } @end
3、设置headerView
理论上任何基于UIView的子类均可以作为tableView的headerView和footerView,但IOS为我们提供了已经整合好的专用类,UITableViewHeaderFooterView,这个类中已经定义了一些属性和方法,如根据重用ID创建类对象等等。
-
为tableView指定headerView有两种方式,第一种是直接为tableView的headerView属性赋值;第二种是实现tableView的代理方法
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
,这种方式可以为每一组指定不同的headerView。代理方法内部实现的思路与单元格基本一致:a、获取魔性信息;
b、创建headerView;
c、为headerView赋值;
d、返回headerView
UITableViewHeaderFooterView只能从代码创建,不能使用storuboard或xib文件创建,因为控件库中没有这个控件
创建一个基于UITableViewHeaderFooterView的子类,因为需要接受group的相关信息,因此需要包含一个LJGroup的类成员,与UITableViewCell类似,也需要封装创建headerView的类方法,并重写它的对象方法
-(instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier
-
在
initWithReuseIdentifier:
方法中,首先需要调用父对象的同名方法,然后创建对应的控件,为控件赋值并设置相关属性知识点:
获取当前屏幕的尺寸可以通过
[UIScreen mainScreen].bounds.size
来获取知识点:
UIButton控件的
contentHorizontalAlignment
属性可以更改控件内容的水平方向的对齐方式,同理UIControlContentVerticalAlignment
属性可以更改控件内容的垂直方向的对齐方式知识点:
UIButton控件的contentEdgeInsets属性可以更改控件内容的内边距
UIButton控件的titleEdgeInsets属性可以更改控件文本框内容的内边距
两者都是需要传递UIEdgeInsets的结构体变量,可以通过UIEdgeInsetsMake( , , , )来创建
知识点:
UIImageView中图片的平铺方式可以通过contentMode属性来设置,该属性需要指定一个类型为UIViewContentMode的枚举量,该枚举量主要有以下成员
typedef NS_ENUM(NSInteger, UIViewContentMode) { //图片拉伸填充至整个UIImageView(图片可能会变形),这也是默认的属性,如果什么都不设置就是它在起作用 UIViewContentModeScaleToFill, //图片拉伸至完全显示在UIImageView里面为止(图片不会变形),这种方式图片完全放置在父容器的范围内,空白区域显示背景色 UIViewContentModeScaleAspectFit, //图片拉伸至图片的的宽度或者高度等于UIImageView的宽度或者高度为止.看图片的宽高哪一边最接近UIImageView的宽高,一个属性相等后另一个就停止拉伸.这种方式图片可能会超出父容器的范围 UIViewContentModeScaleAspectFill, //调用setNeedsDisplay 方法时,就会重新渲染图片 //下面的属性都是不会拉伸图片的,图片也不会缩放,如果图片原始尺寸过大,可能会严重超出父容器的范围 UIViewContentModeRedraw, //中间模式 UIViewContentModeCenter, //顶部 UIViewContentModeTop, //底部 UIViewContentModeBottom, //左边 UIViewContentModeLeft, //右边 UIViewContentModeRight, //左上 UIViewContentModeTopLeft, //右上 UIViewContentModeTopRight, //左下 UIViewContentModeBottomLeft, //右下 UIViewContentModeBottomRight, };
关于UIViewContentMode属性的更多信息请参考
https://www.jianshu.com/p/8c784b59fe6a
知识点:
如果图片超出了父容器的范围,可以通过clipsToBounds属性来设置超出部分是否需要被裁剪,它接收一个Boolean变量
重写LJGroup类成员的set方法,在方法中对标题和在线人数进行赋值
为headerView注册单击事件
-
实现headerView的单击事件
分析:该单击事件中主要实现两个功能,第一是实现箭头的旋转,这可以通过setTransform来实现;第二是实现好友列表的折叠和展开,这可以通过改变该section对应的行数然后刷新单元格来实现,但单击事件是在headerView中实现的,它无法直接刷新单元格,因此需要代理。代理方法虽然可以实现tableView的重载,但是它无法知道当前单击的是哪一个headerView,因此我们需要以单击事件的sender为媒介,传递当前headerView所在的section的索引号,可以通过控件的tag来实现这个目的
//LJGroup类的声明 #import
#import "LJFriend.h" @interface LJGroup : NSObject @property(nonatomic , strong) NSString * name; @property(nonatomic , strong) NSArray * friends; @property(nonatomic , assign) int online; //用于记录当前列表是否为展开状态 @property(nonatomic , assign , getter=isopen) Boolean open; -(instancetype)initWithDic:(NSDictionary *)dic; +(instancetype)groupWithDic:(NSDictionary *)dic; @end //LJGroupHeaderView的声明 #import
#import "LJGroup.h" @class LJGroupHeaderView; //声明LJGroupHeaderView的delegate方法 @protocol LJGroupHeaderViewDelegate @optional -(void)groupHeaderViewDidClicked:(LJGroupHeaderView *)groupHeaderView withSection:(NSInteger)section; @end @interface LJGroupHeaderView : UITableViewHeaderFooterView @property(nonatomic , strong) LJGroup * groupHeaderView_model; //声明delegate属性 @property(nonatomic , weak) id delegate; +(instancetype)groupHeaderViewWith:(UITableView *)tableView; @end //在.m文件中为控件注册单击事件并实现 -(void)groupHeaderViewClick:(UIButton *)sender { //反转当前的Group的展开状态 self.groupHeaderView_model.open = !self.groupHeaderView_model.open; [UIView animateWithDuration:0.3 animations:^{ //根据当前的展开状态判断应该旋转的角度 CGFloat angle = self.groupHeaderView_model.isopen ? M_PI_2 : 0; //使控件旋转 [self.groupname.imageView setTransform:CGAffineTransformMakeRotation(angle)]; }]; //刷新单元格(调用代理方法) //获取当前headerView的tag NSInteger tagnumber = [sender superview].tag; if ([self.delegate respondsToSelector:@selector(groupHeaderViewDidClicked: withSection:)]) { //调用代理方法 [self.delegate groupHeaderViewDidClicked:self withSection:tagnumber ]; } }
//在controller中实现tableView的代理方法来返回headerView -(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { //获取模型信息 LJGroup * group_current = self.groups[section]; //创建headerView LJGroupHeaderView * headerView = [LJGroupHeaderView groupHeaderViewWith:tableView]; //为headerView赋值 headerView.groupHeaderView_model = group_current; //指定headerView的delegate属性 headerView.delegate = self; //将当前section的索引号传递给headerView的tag headerView.tag = section; //返回headerView return headerView; }
//在controller中实现LJGroupHeaderView的代理方法 -(void)groupHeaderViewDidClicked:(LJGroupHeaderView *)groupHeaderView withSection:(NSInteger)section { NSIndexSet * indexsection = [NSIndexSet indexSetWithIndex:section]; //[self.tableView reloadData]太过浪费资源,下面的方法可以进行某个Section的reload,节省资源 [self.tableView reloadSections:indexsection withRowAnimation:UITableViewRowAnimationAutomatic]; }
问题:小箭头的旋转是动画形式执行的,动画尚未执行完毕的时候,这个section就已经开始执行reload的代码了,所以会看到箭头刚开始旋转就闪一下又恢复了。
解决办法:将箭头旋转的行为放置在layoutSubvies方法中,这个方法在子视图被重新布局时被自动调用,默认什么也不做。
-(void)layoutSubviews { [super layoutSubviews]; [UIView animateWithDuration:0.3 animations:^{ //NSLog(@"该动画被执行了"); CGFloat angle = self.groupHeaderView_model.isopen ? M_PI_2 : 0; [self.groupname.imageView setTransform:CGAffineTransformMakeRotation(angle)]; }]; }
由于本人水平有限,不当之处还请批评指正。初学IOS,希望大家一起交流一起进步~