天天看点

TableView实现QQ好友列表效果

概述:本文是黑马程序员UI视频教程第九天,QQ好友列表案例的总结
该案例中主要涉及的知识点有:改变图片在UIButton中的平铺方式、UITableViewHeaderFooterView、设置按钮中图片和文本的内边距和对齐方式、tableView中按组进行重载、自定义delegate协议、layoutSubViews方法、巧妙传递用户的单击信息等内容

基本思路:

  1. 使用UITableviewController,拷贝素材、创建界面、字典转模型,懒加载
  2. 实现tableView的数据源方法
  3. 设置headerView
  4. 实现点击headerView箭头旋转效果
  5. 实现点击headerView好友列表展开效果
  6. 实现VIP会员名称显示为红色

最终效果如下:

TableView实现QQ好友列表效果

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文件来创建单元格

    1. 创建xib单元格
    2. 创建基于UITableViewCell的子类,并与xib文件进行挂接。该子类中应包含LJFriend成员(或者将LJFriend对象作为参数传递给创建单元格的类方法)
    3. 实现创建单元格的类方法,重写LJFriend成员的set方法。在set方法中根据是否是vip设置昵称的字体颜色是否为红色
    #import <UIKit/UIKit.h>
    #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属性的更多信息请参考

    知识点:

    如果图片超出了父容器的范围,可以通过clipsToBounds属性来设置超出部分是否需要被裁剪,它接收一个Boolean变量

    • 重写LJGroup类成员的set方法,在方法中对标题和在线人数进行赋值
    • 为headerView注册单击事件
    • 实现headerView的单击事件

      **分析:**该单击事件中主要实现两个功能,第一是实现箭头的旋转,这可以通过setTransform来实现;第二是实现好友列表的折叠和展开,这可以通过改变该section对应的行数然后刷新单元格来实现,但单击事件是在headerView中实现的,它无法直接刷新单元格,因此需要代理。代理方法虽然可以实现tableView的重载,但是它无法知道当前单击的是哪一个headerView,因此我们需要以单击事件的sender为媒介,传递当前headerView所在的section的索引号,可以通过控件的tag来实现这个目的

      //LJGroup类的声明
      #import <Foundation/Foundation.h>
      #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 <UIKit/UIKit.h>
      #import "LJGroup.h"
      @class LJGroupHeaderView;
      //声明LJGroupHeaderView的delegate方法
      @protocol LJGroupHeaderViewDelegate <NSObject>
      
      @optional
      -(void)groupHeaderViewDidClicked:(LJGroupHeaderView *)groupHeaderView withSection:(NSInteger)section;
      
      @end
      
      @interface LJGroupHeaderView : UITableViewHeaderFooterView
      
      @property(nonatomic , strong) LJGroup * groupHeaderView_model;
      //声明delegate属性
      @property(nonatomic , weak) id<LJGroupHeaderViewDelegate>  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,希望大家一起交流一起进步~