zoukankan      html  css  js  c++  java
  • (二十七)QQ好友列表的实现

    QQ好友列表通过plist读取,plist的结构为一组字典,每个字典内有本组的信息和另外一组字典代表好友。

    要读取plist,选择合适的数据结构,例如NSArray,然后调用initWithContentsOfFile:方法初始化,文件通过mainBundle的pathForResource::方法获取,如下:

     NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"friends" ofType:@"plist"];
     NSArray *data = [[NSArray alloc] initWithContentsOfFile:plistPath];
    


    很明显需要一个Group模型和一个Friend模型,注意到这里的Friend模型有一个BOOL属性来判断是否是vip,根据规范,要把BOOL的get方法重命名为 isXxx,如下:

    注意KVC使用时字典和模型一一对应。

    #import <Foundation/Foundation.h>
    
    @interface Friend : NSObject
    
    @property (nonatomic, copy) NSString *icon;
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, copy) NSString *intro;
    @property (nonatomic, assign, getter = isVip) BOOL vip;  //注意这一行的规范
    
    @end
    
    Tip:将字典的键值对转化为模型的方法为setValuesForKeysWithDictionary: 

    对于Group组的初始化,除去friends属性(装Friend模型),其余的通过KVC就可以注入,可以采用先全部注入,然后取出friends中的每一个字典生成模型,再存入临时的模型数组,最后交给friends属性的方法:

    - (instancetype)initWithDict:(NSDictionary *)dict{
        
        if (self = [super init]) {
            
            //1.注入所有属性,这里的问题是friends里面装的是字典,应该特殊处理
            [self setValuesForKeysWithDictionary:dict];
            //2.特殊处理friends数组中的字典为模型
            NSMutableArray *friendArray = [NSMutableArray array];
            for (NSDictionary *dict in self.friends) {
                Friend *friend = [[Friend alloc] initWithDict:dict];
                [friendArray addObject:friend];
            }
            self.friends = friendArray;
            
        }
        
        return self;
        
    }
    一个细节:模型组使用NSArray而不是NSMutableArray,可以保证数据的安全性(不会被随意修改)。

    团队开发:参数越安全越好。

    好友列表的实现原理是修改TableView的组的头部视图(Plain样式下),修改为下面的结构:


    细节:头部控件也应该进行循环利用,利用一个缓存池。

    Tip:TableView支持的头部视图为UITableViewHeaderFooterView,它有一个UIView *类型的contentView。

    注意:xib暂时不能描述UITableViewHeaderFooterView。


    排查错误:一个控件没有出现

    1.先看看frame的设置,位置和尺寸

    2.检查控件的hidden属性是否为YES

    3.检查有没有加入父控件中

    4.看透明度alpha是否<0.01

    5.被其他空间挡住

    6.父控件有没有前面5个问题

    打印结构体的方法:NSStringFromXxx函数转化为NSString对象。

    注意:任何UIView控件的init方法中,获取到的framebounds都是0

    #warning xxx的应用:编译器会在当前位置产生一个警告,用于提醒。

    Tip:HeaderFooterView的宽度永远是它所在的TableView的宽度。

    Tip:如果每一组的HeaderFooterView宽度相同,不必调用函数,直接使用 self.tableView.sectionHeaderHeight =44; 来设置。

     同理self.tableView.rowHeight = 50;表示每一行cell的高度。

    Tip:Xcode的插件路径:/Users/soulghost/Library/Application Support/Developer/Shared/Xcode/Plug-ins/


    因为任何UIView的init方法中都获取不到frame和bounds,因此应该通过layoutSubviews方式获取到各个控件的frame,然后进行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);
        
    }
    


    具体的实现和前面的聊天实现差不多,但是设置frame的时机变为了HeaderView的layoutSubviews方法,因为只有在这里才能得到HeaderView的尺寸,进而计算左侧的按钮和右侧的Label尺寸。

    注意因为指向View的指针为弱指针,先用强指针指向一个新建的View,加入视图后再交付给弱指针,从而保证不会被释放。


    下面分析整个过程:

    为了实现朋友列表,需要上图那样的View作为HeaderView,使用tableView的方法(注意控制器必须是UITableView)

    tableView: viewForHeaderInSection:方法来设定HeaderView的视图,为了封装性,定义一个类HeaderView来设置该视图:

    @class FriendsGroup;
    
    @interface HeaderView : UITableViewHeaderFooterView
    
    @property (nonatomic, strong) FriendsGroup *group;
    
    + (instancetype)headerViewWithTableView:(UITableView *)tableView;
    
    @end
    因为HeaderView中的内容包括组名和组内的在线情况,因此应该维护一个group模型,因为HeaderView采用缓存池来进行性能优化,因此要传入tableView。

    HeaderView的实现比较麻烦,initWithReuse...方法用于得到一个可重用的或者新的HeaderView,如果是新的,就创建新的Label和Button,注意一个细节,为了实现类似下图的效果:

    按钮的图片与左侧有距离,按钮的图片与按钮的标题也有距离,这是通过设置内边距实现的,设置content的内边距(EdgeInset)可以使得按钮整体左移,使用imageEdgeInset或者titleEdgeInset都可以实现按钮图片和标题的距离,需要注意的是弱指针的赋值技巧和在这里获取控件的frame(init中)是nil。

    第一个方法是用于生成一个特定ID的HeaderView,进行初步的初始化(注意上面的描述),然后返回,它是供第四个方法调用用的。

    第二个方法就是所谓的HeaderView尺寸被系统改变时,frame可以获取了,在这里设置各个控件的尺寸。

    第三个方法重写了group的set方法,用于设定控件的内容,调用时机是取出HeaderView并且传递模型时。

    第四个方法是先从缓存池中取得HeaderView,失败则调用第一个方法获取一个HeaderView,然后返回,这个方法的主要目的是内存优化。

    @interface HeaderView ()
    
    @property (nonatomic, weak) UILabel *countView;
    @property (nonatomic, weak) UIButton *nameView;
    
    @end
    
    @implementation HeaderView
    
    - (id)initWithReuseIdentifier:(NSString *)reuseIdentifier{
        
        
        //初始化时,headerFooterView的frame和bounds暂时没有值。
        //任何UIView控件的init方法中,获取到的frame和bounds都是0
        if (self = [super initWithReuseIdentifier:reuseIdentifier]) {
            //添加底部Button
            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];
            //左对齐+内边距实现靠左。设置图片和文字的内边距(设置Title的内边距)。
            [nameView setTitle:@"我的好友" 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);
            
            [self.contentView addSubview:nameView];
            self.nameView = nameView;
            //添加好友数目Label
            UILabel *countView = [[UILabel alloc] init];    
            countView.textAlignment = NSTextAlignmentRight;
            countView.textColor = [UIColor grayColor];
            [self.contentView addSubview:countView];
            self.countView = countView;
        
        }
        
        return self;
        
    }
    /**
     *  布局子控件,当一个控件的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);
        
    }
    
    - (void)setGroup:(FriendsGroup *)group{
        
        _group = group;
        
        //设置按钮文字
        [self.nameView setTitle:group.name forState:UIControlStateNormal];
        //设置在线数
        self.countView.text = [NSString stringWithFormat:@"%d/%d",group.online,group.friends.count];
    }
    
    + (instancetype)headerViewWithTableView:(UITableView *)tableView{
        
        
        static NSString *ID = @"group";
        
        HeaderView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:ID];
        
        if (headerView == nil) {
            headerView = [[HeaderView alloc] initWithReuseIdentifier:ID];
            
        }
        return headerView;
    }
    
    @end
    
    通过下面的方法调用,就可以成功得到多个headerView供系统显示:

    - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
        //1.创建头部空间
        HeaderView *header = [HeaderView headerViewWithTableView:tableView];
        //2.给header设置数据(传递模型)
        header.group = self.groups[section];
        
        return header;
    }


    对于显示好友信息的Cell,和上一节的方法基本一致,但是比较简单,直接使用系统的SubTitle样式即可。
    Cell维护一个Friend模型,并且在为Cell传递模型时(控制器里进行)初始化数据,因此要重写friendData的set方法(它是Friend成员变量,为了和C++的友元区分,固命此名)。
    对于cell的初始化,有固定的写法,init方法用于从父类创建一个特定ID的Cell,一个用于内存优化,Cell的代码如下:

    @implementation FriendCell
    
    - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
        self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
        return self;
    }
    
    
    + (instancetype)cellWithTableView:(UITableView *)tableView{
        
        static NSString *ID = @"friend";
        FriendCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
        if (cell == nil) {
            cell = [[FriendCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
        }
        return cell;
        
    }
    
    -(void)setFriendData:(Friend *)friendData{
        
        _friendData = friendData;
        
        self.imageView.image = [UIImage imageNamed:friendData.icon];
        self.textLabel.text = friendData.name;
        self.detailTextLabel.text = friendData.intro;
        
    }

    这样,只要在取得Cell的函数里,通过cellWithTableView获取cell,然后传入模型的同时会调用set方法设定数据,最后返回的Cell便是最终的Cell。




  • 相关阅读:
    Python格式化输出
    每天写点shell脚本 (持续更新)
    linux limits.conf 配置
    ELK 日志分析系统
    开源大数据处理工具
    glusterFS分布式存储部署流程
    glusterFS的部署流程
    parted命令详解
    /proc文件系统
    /proc文件系统(二):/proc/<pid>/stat
  • 原文地址:https://www.cnblogs.com/aiwz/p/6154226.html
Copyright © 2011-2022 走看看