那么今天给同学写了一个QQ好友列表展示的Demo,涉及很多的内部细节以及高度封装自定义的cell和自定义view,那么内部所用知识和细节全部呈现在代码和注释中,那么废话不多说直接上代码,先看效果图!
//
// ZZFriendGroup.h
// 12-QQ好友列表展示
//
// Created by 周昭 on 16/12/12.
// Copyright © 2016年 HT_Technology. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface ZZFriendGroup : NSObject
/**
* 组的姓名
*/
@property (nonatomic, copy) NSString *name;
/**
* 好友数
*/
@property (nonatomic, strong) NSArray *friends;
/**
* 在线数
*/
@property (nonatomic, assign) NSInteger online;
/**
* 是否展开 这里提供一个getter方法出来 外部访问 如 model.isOpened 就很显而已懂 写和不写 一看就有大神的区别
*/
@property (nonatomic, assign, getter = isOpened) BOOL opened;
- (instancetype)initWithDict:(NSDictionary *)dict;
+ (instancetype)groupWithDict:(NSDictionary *)dict;
@end
//
// ZZFriendGroup.m
// 12-QQ好友列表展示
//
// Created by 周昭 on 16/12/12.
// Copyright © 2016年 HT_Technology. All rights reserved.
//
#import "ZZFriendGroup.h"
#import "ZZFriends.h"
@implementation ZZFriendGroup
- (instancetype)initWithDict:(NSDictionary *)dict
{
if (self = [super init]) {
// 1.通过kvc注入所有的属性
[self setValuesForKeysWithDictionary:dict];
// 2.不一样的属性特殊处理
NSMutableArray *friendArr = [NSMutableArray array];
for (NSDictionary *dict in self.friends) {
ZZFriends *friend = [ZZFriends friendWithDict:dict];
[friendArr addObject:friend];
}
self.friends = friendArr;
}
return self;
}
+ (instancetype)groupWithDict:(NSDictionary *)dict
{
return [[self alloc] initWithDict:dict];
}
@end
//
// ZZFriends.h
// 12-QQ好友列表展示
//
// Created by 周昭 on 16/12/12.
// Copyright © 2016年 HT_Technology. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface ZZFriends : NSObject
/**
* 图标
*/
@property (nonatomic, copy) NSString *icon;
/**
* 简介
*/
@property (nonatomic, copy) NSString *intro;
/**
* 名字
*/
@property (nonatomic, copy) NSString *name;
/**
* vip : 提供getter方法 外部访问 例如 self.isVip 都能准确明白是不是会员 这样写一看就是大神
*/
@property (nonatomic, assign, getter = isVip) BOOL vip;
- (instancetype)initWithDict:(NSDictionary *)dict;
+ (instancetype)friendWithDict:(NSDictionary *)dict;
@end
//
// ZZFriends.m
// 12-QQ好友列表展示
//
// Created by 周昭 on 16/12/12.
// Copyright © 2016年 HT_Technology. All rights reserved.
//
#import "ZZFriends.h"
@implementation ZZFriends
- (instancetype)initWithDict:(NSDictionary *)dict
{
if (self = [super init]) {
[self setValuesForKeysWithDictionary:dict];
}
return self;
}
+ (instancetype)friendWithDict:(NSDictionary *)dict
{
return [[self alloc] initWithDict:dict];
}
@end
//
// ZZFriendCell.h
// 12-QQ好友列表展示
//
// Created by 周昭 on 16/12/12.
// Copyright © 2016年 HT_Technology. All rights reserved.
//
#import <UIKit/UIKit.h>
@class ZZFriends;
@interface ZZFriendCell : UITableViewCell
+ (instancetype)cellWithTableView:(UITableView *)tableView;
#pragma mark - 这里写出friend 作为成员属性的变量名 会发现是系统的关键词 因为Xcode 支持 OC C C++ friend 为C++中的友元函数\友元类
@property (nonatomic, strong) ZZFriends *friendData;
@end
//
// ZZFriendCell.m
// 12-QQ好友列表展示
//
// Created by 周昭 on 16/12/12.
// Copyright © 2016年 HT_Technology. All rights reserved.
//
#import "ZZFriendCell.h"
#import "ZZFriends.h"
@implementation ZZFriendCell
+ (instancetype)cellWithTableView:(UITableView *)tableView
{
static NSString *ID = @"friend";
ZZFriendCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
cell = [[ZZFriendCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}
return cell;
}
- (void)setFriendData:(ZZFriends *)friendData
{
_friendData = friendData;
#warning - 原来这里傻逼了 属性值不对所以直接蹦了
self.imageView.image = [UIImage imageNamed:friendData.icon];
self.textLabel.text = friendData.name;
self.detailTextLabel.text = friendData.intro;
if (friendData.isVip) {
self.detailTextLabel.textColor = [UIColor redColor];
} else {
self.detailTextLabel.textColor = [UIColor blackColor];
}
}
@end
//
// ZZHeaderView.h
// 12-QQ好友列表展示
//
// Created by 周昭 on 16/12/12.
// Copyright © 2016年 HT_Technology. All rights reserved.
//
#import <UIKit/UIKit.h>
@class ZZFriendGroup,ZZHeaderView;
@protocol ZZHeaderViewDelegate <NSObject>
@optional
- (void)headerViewDidClick:(ZZHeaderView *)headerView withSection:(NSInteger)section;
@end
#warning - 这里如果你有疑问你觉得为什么都长的一样不用xib来描述 UITableViewHeaderFooterView 这个东西是比较高级的所以目前无法用xib来描述
@interface ZZHeaderView : UITableViewHeaderFooterView
+ (instancetype)headerViewWithTableView:(UITableView *)tableView;
@property (nonatomic, strong) ZZFriendGroup *group;
@property (nonatomic, assign) NSInteger section;
@property (nonatomic, weak) id <ZZHeaderViewDelegate> delegate;
@end
//
// ZZHeaderView.m
// 12-QQ好友列表展示
//
// Created by 周昭 on 16/12/12.
// Copyright © 2016年 HT_Technology. All rights reserved.
//
#import "ZZHeaderView.h"
#import "ZZFriendGroup.h"
@interface ZZHeaderView()
@property (nonatomic, weak) UIButton *nameView;
@property (nonatomic, weak) UILabel *countView;
@end
@implementation ZZHeaderView
+ (instancetype)headerViewWithTableView:(UITableView *)tableView
{
static NSString *ID = @"group";
#warning - 当你熟悉了cell的重用那么到这里你很容易推导出来头部和尾部的重用机制
// ZZHeaderView *header = [tableView dequeueReusableHeaderFooterViewWithIdentifier:ID];
#warning - (目前这里在刷新头部的view的时候有问题所以暂时还在思考如何优化)
ZZHeaderView *header = [[ZZHeaderView alloc] initWithReuseIdentifier:ID];
// if (header == nil) {
// header = [[ZZHeaderView alloc] initWithReuseIdentifier:ID];
// }
return header;
}
- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithReuseIdentifier:reuseIdentifier]) {
// 1.一个按钮来承载下标和该组的标题
UIButton *nameView = [[UIButton alloc] init];
[nameView setBackgroundImage:[UIImage imageNamed:@"buddy_header_bg"] forState:UIControlStateNormal];
[nameView setBackgroundImage:[UIImage imageNamed:@"buddy_header_bg_highlighted"] forState:UIControlStateHighlighted];
[nameView setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[nameView addTarget:self action:@selector(nameViewClick) forControlEvents:UIControlEventTouchUpInside];
nameView.titleLabel.font = [UIFont systemFontOfSize:15.0f];
// 这里是更改水平方向 内部属性如何布局
nameView.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
// 设置整个按钮的内边距
nameView.contentEdgeInsets = UIEdgeInsetsMake(0, 15, 0, 0);
// 设置图片或者文字的内边距
nameView.titleEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 0);
// 设置图片旋转始终居中
nameView.imageView.contentMode = UIViewContentModeCenter;
// imageView 默认会剪切
nameView.imageView.clipsToBounds = NO;
#pragma mark - 那么这里添加到夫类中我们就要考虑和cell一样 有没有contentView属性
[self.contentView addSubview:nameView];
self.nameView = nameView;
// 2.一个label来承载在线人数
UILabel *countView = [[UILabel alloc] init];
countView.textColor = [UIColor grayColor];
countView.font = [UIFont systemFontOfSize:14.5f];
countView.textAlignment = NSTextAlignmentRight;
[self.contentView addSubview:countView];
self.countView = countView;
}
return self;
}
#warning - 这里我们才能确定子控件的frame 所以只能在这里进行布局 一看这个方法名就这么有水准
- (void)layoutSubviews
{
[super layoutSubviews];
self.nameView.frame = self.bounds;
CGFloat countViewW = 100;
CGFloat countViewX = self.frame.size.width - countViewW - 15;
CGFloat countViewY = 0;
CGFloat countViewH = self.frame.size.height;
self.countView.frame = CGRectMake(countViewX, countViewY, countViewW, countViewH);
}
- (void)setGroup:(ZZFriendGroup *)group
{
_group = group;
#warning - 那么这里我们可以看到按钮的设置 其属性全部默认是居中的那么我们就应该做出相应的调整
// 设置标题和图标
[self.nameView setTitle:group.name forState:UIControlStateNormal];
[self.nameView setImage:[UIImage imageNamed:@"buddy_header_arrow"] forState:UIControlStateNormal];
// 设置在线人数
self.countView.text = [NSString stringWithFormat:@"%ld/%ld",group.online,group.friends.count];
}
- (void)setSection:(NSInteger)section
{
_section = section;
self.nameView.tag = section;
}
/**
* 点击该行
*/
- (void)nameViewClick
{
// 0.设置展开或者隐藏
self.group.opened = !self.group.opened;
// 1.通知代理刷新表格
if ([self.delegate respondsToSelector:@selector(headerViewDidClick:withSection:)]) {
[self.delegate headerViewDidClick:self withSection:self.nameView.tag];
}
#warning - 我们可以拿到按钮内部所有的东西 如果要旋转 我们应该用到transform如果不能直接旋转那么分析首先可能是autoLayout 限制了!如果不能执行这些操作那么也要分析 尤其在tableView 中只要刷新表格那么他就不会管缓存池 去重新创建的!内存地址不一样!
// 2.那么要执行旋转很多人会考虑在这里处理 那么这也是个难点(点了没反应这是因为旧的你旋转了但是他直接创建了一个新的移除了旧的所以这里肯定不行)
// if (self.group.isOpened) {
// self.nameView.imageView.transform = CGAffineTransformMakeRotation(M_PI_2);
// } else {
// self.nameView.imageView.transform = CGAffineTransformMakeRotation(0);
// }
}
/**
* 当一个控件被添加到父控件中的时候就会被调用
*/
- (void)didMoveToSuperview
{
if (self.group.isOpened) {
self.nameView.imageView.transform = CGAffineTransformMakeRotation(M_PI_2);
} else {
self.nameView.imageView.transform = CGAffineTransformMakeRotation(0);
}
}
//- (void)willMoveToSuperview:(UIView *)newSuperview
//{
//
//
// if (self.group.isOpened) {
// self.nameView.imageView.transform = CGAffineTransformMakeRotation(M_PI_2);
// } else {
// self.nameView.imageView.transform = CGAffineTransformMakeRotation(0);
// }
//}
@end
//
// ZZFriendTableViewController.h
// 12-QQ好友列表展示
//
// Created by 周昭 on 16/12/12.
// Copyright © 2016年 HT_Technology. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface ZZFriendTableViewController : UITableViewController
@end
//
// ZZFriendTableViewController.m
// 12-QQ好友列表展示
//
// Created by 周昭 on 16/12/12.
// Copyright © 2016年 HT_Technology. All rights reserved.
//
#import "ZZFriendTableViewController.h"
#import "ZZFriendGroup.h"
#import "ZZFriends.h"
#import "ZZFriendCell.h"
#import "ZZHeaderView.h"
@interface ZZFriendTableViewController()<ZZHeaderViewDelegate>
@property (nonatomic, strong) NSArray *groups;
@end
@implementation ZZFriendTableViewController
#pragma mark - 那么这里写给同学们讲一下为什么有时候用 NSArray 有时候用 NSMutableArray 当你的数据固定的时候你已经写好了不希望你的同事随便的改那肯定是用 NSArray 那么他就不能调用[groups addobject:]方法
- (NSArray *)groups
{
if (_groups == nil) {
NSArray *dictArray = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"friends.plist" ofType:nil]];
NSMutableArray *groupArray = [NSMutableArray array];
for (NSDictionary *dict in dictArray) {
ZZFriendGroup *group = [ZZFriendGroup groupWithDict:dict];
[groupArray addObject:group];
}
_groups = groupArray;
}
return _groups;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// 1.设置每个cell的高度
self.tableView.rowHeight = 50;
// 2.设置头部view的高度
self.tableView.sectionHeaderHeight = 50;
}
/**
* 隐藏状态栏
*/
- (BOOL)prefersStatusBarHidden
{
return YES;
}
#pragma mark - tableView 数据源和代理方法
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return self.groups.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
#warning - 那么我们如何实现 展开和显示呢?这里 就直接来实现一下 (当这一组没有数据的时候那么就合并了 所有我就直接说我的方案 点击按钮的时候来根据点击按钮的状态来判断开或者关)
ZZFriendGroup *group = self.groups[section];
return group.isOpened ? group.friends.count : 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
#pragma mark - 那么我们这里进行需求驱动开发
// 1.创建一个属于自己的cell
ZZFriendCell *cell = [ZZFriendCell cellWithTableView:tableView];
// 2.给cell传递模型
ZZFriendGroup *group = self.groups[indexPath.section];
cell.friendData = group.friends[indexPath.row];
// 3.返回cell
return cell;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
// 1.那么这里就要自定义头部的view
ZZHeaderView *header = [ZZHeaderView headerViewWithTableView:tableView];
header.delegate = self;
// 2.给自定义view传递模型
ZZFriendGroup *group = self.groups[section];
header.group = group;
header.section = section;
NSLog(@"%p ------ %ld",header, section);
// 3.赋值
return header;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
#pragma mark - ZZHeaderViewDelegate方法
- (void)headerViewDidClick:(ZZHeaderView *)headerView withSection:(NSInteger)section
{
[self.tableView reloadData];
}
@end