静态单元格
效果演示:
我们看到这个界面之后, 其实我们可以利用我们之前的知识来完成, 但是今天我们讲的是利用静态单元格完成(这样就不需要写任何的代码)
静态单元格的处理环境就是对于和我们应用的服务器没有任何关系的(只有程序更新该界面才会改变的界面)
首先, 按照我们以前的方式: 让我们的控制器继承自tableViewController
然后将我们的storyboard中的view换成tableView
我们上面的右边一个小框框上面那个是代表的是动态的cell就是数据可以动态的
小框框的是静态的, 里面的数据是死的, 没有办法改变的.
然后我们cell的style是Groups
这上面的小框框里面表示的是有多少组, 但是首先我们要先写一个1组, 这样当我们完成了这一个cell之后, 在改成3组时, 他会自动复制我们之前设置好的这一组 .
这里面的row是每一组cell里面有几个cell
这个框框可以选择我们cell的类型, 以及下面可以添加图片
我们只需要将我们的图片名称填到里面就可以了
然后, 我们的文字, 只需要我们连续点击两次这个位置:
就可以显示出这种样式, 这样我们就可以修改里面的内容了. 最后我们再直接将以前的那个多少组, 再改成3组依次就可以了.
好友列表- 01加载数据模型
效果演示;
我们注意到, 在我们的这个界面中, 一个是好友列表, 一个是好友组列表
所以我们不妨创建两个模型, 一个是好友信息数据模型, 一个是好友组信息数据模型
在这加载数据模型, 是为了复习 我就直接将代码拿出来了好吧:
FriendGroups.h文件
#import <Foundation/Foundation.h>
@interface GJFriendGroup : NSObject
@property (nonatomic, copy) NSString *name;
/**
* 数组中装的都是GJFriend模型
*/
@property (nonatomic, strong) NSArray *friends;
@property (nonatomic, assign) int online;
+ (instancetype)groupWithDict:(NSDictionary *)dict;
- (instancetype)initWithDict:(NSDictionary *)dict;
@end
FriendGroups.m文件
#import "GJFriendGroup.h"
#import "GJFriend.h"
@implementation GJFriendGroup
+ (instancetype)groupWithDict:(NSDictionary *)dict
{
return [[self alloc] initWithDict:dict];
}
- (instancetype)initWithDict:(NSDictionary *)dict
{
if (self = [super init]) {
// 1.注入所有属性
[self setValuesForKeysWithDictionary:dict];
// 2.特殊处理friends属性
NSMutableArray *friendArray = [NSMutableArray array];
for (NSDictionary *dict in self.friends) {
GJFriend *friend = [GJFriend friendWithDict:dict];
[friendArray addObject:friend];
}
self.friends = friendArray;
}
return self;
}
@end
friend.h文件
#import <Foundation/Foundation.h>
@interface GJFriend : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *icon;
@property (nonatomic, copy) NSString *intro;
@property (nonatomic, assign, getter = isVip) BOOL vip;
+ (instancetype)friendWithDict:(NSDictionary *)dict;
- (instancetype)initWithDict:(NSDictionary *)dict;
@end
friend.m文件
#import "GJFriend.h"
@implementation GJFriend
+ (instancetype)friendWithDict:(NSDictionary *)dict
{
return [[self alloc] initWithDict:dict];
}
- (instancetype)initWithDict:(NSDictionary *)dict
{
if (self = [super init]) {
[self setValuesForKeysWithDictionary:dict];
}
return self;
}
@end
控制器.m文件
- (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) {
GJFriendGroup *group = [GJFriendGroup groupWithDict:dict];
[groupArray addObject:group];
}
_groups = groupArray;
}
return _groups;
}
好友列表 - 02显示好友数据
我们首先将所有的数据给弄到界面里面去, 然后再考虑其他的, 所以这个又是直接上代码:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return self.groups.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
GJFriendGroup *group = self.groups[section];
return group.friends.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1.创建cell
static NSString *ID = @"friend";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}
// 2.设置cell的数据
GJFriendGroup *group = self.groups[indexPath.section];
GJFriend *friend = group.friends[indexPath.row];
cell.imageView.image = [UIImage imageNamed:friend.icon];
cell.textLabel.text = friend.name;
cell.detailTextLabel.text = friend.intro;
return cell;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
GJFriendGroup *group = self.groups[section];
return group.name;
}
@end
好友列表 -03添加头部控件
之前我们在做汽车品牌展示的 时候就已经知道了, 他有一个头部的label用来显示我我们的头部标题的, 现在其实我们的这个头部不仅仅可以使label同时也可以是UIView当然 他的命名可不是这个, 他的命名是: UITableViewHeaderFooterView
观察上面的效果图, 我们发现, 如果让他作为我们每一个好友列表的头部就需要具备四个控件(UIImage, UILabel, UILablel, UIButton), 当然,这种方法是不可取的, 因为 当我们点击我们那个小箭头的时候, 我们的好友列表也会展开, 所以我们采用的措施是:
用一个大的UIButton覆盖整个UITableViewHeaderFooterView, 然后再在UIButton上面添加一个UILabel, 这样我们的UITableViewHeaderFooterView就具备了以上四个控件(UIButton自身还具备了UIImage, UILabel)
下面开始写:
首先, 我们要有一个继承自UITableViewHeaderFooterView的类(GJHeaderView)
然后在这个类中设置我们的UITableViewHeaderFooterView
- 写一个方法, 方便我们的控制器调用这个方法来创建头部控件
+ (instancetype)headerViewWithTableView:(UITableView *)tableView;
2 . 在该方法里面设置我们headerView
其实我们的头部也是和我们的UIViewCell一样可以重复利用的, 所以他在创建的时候, 类似于我们的UIViewCell:
+ (instancetype)headerViewWithTableView:(UITableView *)tableView
{
static NSString *ID = @"header";
GJHeaderView *header = [tableView dequeueReusableHeaderFooterViewWithIdentifier:ID];
if (header == nil) {
header = [[GJHeaderView alloc] initWithReuseIdentifier:ID];
}
return header;
}
注意这个方法的调用: header = [[GJHeaderView alloc] initWithReuseIdentifier:ID];
我们如果想在创建头部的时候, 自动为我们添加我们说的几个子控件 就需要改写这个方法(直接在这个方法里面创建)
- (id)initWithReuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithReuseIdentifier:reuseIdentifier]) {
// 添加子控件
// 1.添加按钮
UIButton *nameView = [UIButton buttonWithType:UIButtonTypeCustom];
[self.contentView addSubview:nameView];
self.nameView = nameView;
// 2.添加好友数
UILabel *countView = [[UILabel alloc] init];
[self.contentView addSubview:countView];
self.countView = countView;
}
return self;
}
大家注意到了吧, 就是我们的控件的创建并没有设置我们每一个子控件的frame
原因:
首先, 我们子控件的创建一定需要我们这个HeaderView
的frame
其次, 其实由于我们的这个HeaderView
在init....方法中, 他一开始初始化时没有任何值的, 所以当我们在这个方法里面设置我们子控件的frame(需要调用HeaderView的frame)时 , 是没有值的, 所以苹果公司为我们提供了一个方法: - (void)layoutSubviews
而且这个方法是不需要我们自己刻意调用的而是 **当一个控件的frame发生改变的时候就会调用. **
所以我们一般是在这个方法中设置我们子控件的frame:
- (void)layoutSubviews
{
#warning 一定要调用super的方法
[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);
}
而在控制器中我们是这么调用这个方法来创建headerView的:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
// 1.创建头部控件
GJHeaderView *header = [GJHeaderView headerViewWithTableView:tableView];
// 2.给header设置数据(给header传递模型)
return header;
}
好友列表 -04设置头部数据
关于我们添加头部控件的数据 , 其实也是很简单的, 只是注意的是, 我们的关于头部控件的子控件的排放问题
当我们直接将我们的子控件添加到我们的头部控件时, 他会居中摆放, 这样不是我们想要的结果, 所以我们就要自己设置
// 设置按钮的内容左对齐 nameView.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
这个方法就是 直接设置我们每一个子控件都要左对齐, 然后, 我们的控件也不能直接摆放到里面, 最好是有一些空隙 所以这就需要另一个方法:
// 设置按钮的内边距
// nameView.imageEdgeInsets
nameView.titleEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 0);
nameView.contentEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 0);
所以这样我们的上面一个头部标题和箭头都解决了, 剩下的就是关于右边的好友数了
- (void)setGroup:(GJFriendGroup *)group
{
_group = group;
// 1.设置按钮文字(组名)
[self.nameView setTitle:group.name forState:UIControlStateNormal];
// 2.设置好友数(在线数/总数)
self.countView.text = [NSString stringWithFormat:@"%d/%d", group.online, group.friends.count];
}
这样就完成了我们的头部数据的添加, 具体的代码如下:
// 添加子控件
// 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.imageEdgeInsets
nameView.titleEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 0);
nameView.contentEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 0);
[self.contentView addSubview:nameView];
self.nameView = nameView;
// 2.添加好友数
UILabel *countView = [[UILabel alloc] init];
countView.textAlignment = NSTextAlignmentRight;
countView.textColor = [UIColor grayColor];
[self.contentView addSubview:countView];
self.countView = countView;
然而设置我们的好友数, 就是我们上面的一个方法.
还有一点就是关于我们的封装:
在我们的上面的控制器牵扯到一个cell的创建:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
其实我们完全可以自定义一个cell来将原先的创建cell的过程封装起来
该自定义cell的名称就叫做: friendCell
注意的是, 我们的这个cell是需要我们的friend这个模型数据的, 因为我们这个cell在创建的时候需要的数据都是从这个模型中取出来的
**friendCell.h文件: **
#import <UIKit/UIKit.h>
@class GJFriend;
@interface GJFriendCell : UITableViewCell
+ (instancetype)cellWithTableView:(UITableView *)tableView;
// friend是C++的关键字,不能用friend作为属性名
@property (nonatomic, strong) GJFriend *friendData;
@end
friendCell.m文件
#import "GJFriendCell.h"
#import "GJFriend.h"
@implementation GJFriendCell
+ (instancetype)cellWithTableView:(UITableView *)tableView
{
static NSString *ID = @"friend";
GJFriendCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
cell = [[GJFriendCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}
return cell;
}
- (void)setFriendData:(MJFriend *)friendData
{
_friendData = friendData;
self.imageView.image = [UIImage imageNamed:friendData.icon];
self.textLabel.text = friendData.name;
self.detailTextLabel.text = friendData.intro;
}
@end
这个就是我们封装的代码:
好友列表 -05展开合并好友列表
其实, 我们可以换一种思路去想如果让我们的好友列表合并, 原来模式下, 我们的好友列表是自动展开的, 所以我们在想如何将他合并
其实我们可以这样做:
当我们的好友列表展开时 , 我们就不动 , 一旦需要合并的时候, 我们将我们的friend模型改成0, 然后在重新刷新表格, 这样就做到我们想要的那个效果
**1. 我们要监听按钮的点击: **
[nameView addTarget:self action:@selector(nameViewClick) forControlEvents:UIControlEventTouchUpInside];
这样当我们的按钮被点击的时候, 它就会调用我们的nameViewClick
这个方法, 然后我们只需要在这个方法里面完成展开合并好友列表的这个功能
2 . 思考: 如何使我们的列表里面的好友有时候显示, 有时候不显示???
很简单, 我们利用BOOL类型判断我们的列表是否展开, 如果是YES(表示是展开的)就显示好友, 如果是NO就在这个方法里面返回一个0, 就代表着, 所有组的好友数为0:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
如何写: 利用三目运算:
return (group.isOpened ? group.friends.count : 0);
3 . 在我们friendGroup里面添加一个BOOL类型的属性: (是否要显示好友列表)
/**
* 标识这组是否需要展开, YES : 展开 , NO : 关闭
*/
@property (nonatomic, assign, getter = isOpened) BOOL opened;
4 . 在headerView的nameViewClick
方法中按位取反
意思就是每当我们点击头部控件的时候, 如果这个isOpened是YES就取反为NO, 这样就可以完成我们第一次点击头部控件, 为展开, 再点一次isOpened变为NO了, 就合并, 然后又是展开
// 1.修改组模型的标记(状态取反)
self.group.opened = !self.group.isOpened;
**5. 刷新表格: **
对于我们整个的文件来说, 谁最适合刷新表格???当然是控制器了, 所以我们当我们的按钮被点击的时候, 我们要通知我们的控制器, 按钮被人点了, 所以我们就需要用到** 代理**
先写好协议:
@protocol GJHeaderViewDelegate <NSObject>
@optional
- (void)headerViewDidClickedNameView:(GJHeaderView *)headerView;
@end
然后再创一个协议属性:
@property (nonatomic, weak) id<MJHeaderViewDelegate> delegate;
- (void)nameViewClick
{
// 1.修改组模型的标记(状态取反)
self.group.opened = !self.group.isOpened;
// 2.刷新表格
if ([self.delegate respondsToSelector:@selector(headerViewDidClickedNameView:)]) {
[self.delegate headerViewDidClickedNameView:self];
}
其次 , 在这个方法中告诉我么你的代理, 按钮被点击了, 把自己传给代理, 告诉代理是自己被人点击了
最后, 控制器遵守协议,成为我们头部控件的代理 然后实现协议方法:
@interface GJViewController () <GJHeaderViewDelegate>
实现方法:
#pragma mark - headerView的代理方法
/**
* 点击了headerView上面的名字按钮时就会调用
*/
- (void)headerViewDidClickedNameView:(GJHeaderView *)headerView
{
[self.tableView reloadData];
}
**6 关于左边下箭头的旋转 **
当我们的列表展开后, 我们的小箭头应该向下旋转90°, 但是,首先提示我们的这个工能是不可以在nameClick这个方法中写的, 因为, 每当我们调用这个方法时, 我们的表格就会重新刷新一次, 所以就算我们在这里修改, 也无济于事
所以, 苹果官方又给我们推荐了一个新的方法, 该方法不需要我们去调用, 他会在合适的时间自动调用
/**
* 当一个控件被添加到父控件中就会调用
*/
- (void)didMoveToSuperview
{
if (self.group.opened) {
self.nameView.imageView.transform = CGAffineTransformMakeRotation(M_PI_2);
} else {
self.nameView.imageView.transform = CGAffineTransformMakeRotation(0);
}
}
注释写的就是我们这个方法是在什么时候调用的
**但是: **
就算我们这样弄好了, 图片也可以旋转了, 但是效果不是很好, 图片会变形, 因为我们的UIImage
会自动拉伸, 以及裁剪, 所以我们需要在设置图片的代码里添加这样两句代码:
// 设置按钮内部的imageView的内容模式为居中
nameView.imageView.contentMode = UIViewContentModeCenter;
// 超出边框的内容不需要裁剪
nameView.imageView.clipsToBounds = NO;
这样, 我们的这个程序, 就到了最后一步, 设置显示会员:
好友列表- 06 标识会员
很简单, 在我们的friendCell里面直接来个三目运算就可以了:
self.textLabel.textColor = friendData.isVip ? [UIColor redColor] : [UIColor blackColor];
APP管理
现在我们又回到我们一开始的那一项, 单元格, 我们删掉原来的UIView, 重新拖入一个UITableView, 他会自动在上面生成一个单元格(Prototype Cells
), 上面我们介绍的是静态单元格, 现在我们要利用他做一个动态的单元格 (需要写代码的) 利用动态单元格做一个以前做过的应用管理 :
效果展示:
然后, 由于我们系统自带的Prototype Cells
没有自带我们的这种格式,而且, 在系统中原有的格式中 ,我们再添加任何子控件, 所以我们只有选择, 自定义:
其次, 原来的那个数据转模型代码我就不写了, 直接写一个appCell
其实, 我们可以将我们的这个动态单元格看成一个Xib 只是他要比Xib方便的多
对于我们在创建一个新的cell时候 , 我们一般会给cell一个标识, 这样方便我们以后从缓存池中找, 所以, 同样的我们的动态单元格, 也有一个标识:
然后我们在控制器里面创建的代码就变成这样了:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.apps.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
GJAppCell *cell = [tableView dequeueReusableCellWithIdentifier:@"app"];
cell.delegate = self;
cell.app = self.apps[indexPath.row];
return cell;
}
对比以前的代码, 下奶在看看现在的代码:
GJAppCell *cell = [tableView dequeueReusableCellWithIdentifier:@"app"];
只要这一句代码就完成了, 以前我们还需要用一个判断语句来判断缓存池中是否有cell, 现在不需要了
而且, 可以发现, 一个cell的创建是只需要表示(Identifier), 那么我们完全可以多创建几个Prototype Cells 只需要写上不同的标识而已, 例如:
利用代理, 显示已下载, 和弹出框框
首先我们设置代理的方法已经说过很多次了, 今天就不说了,
- 首先, 我们需要得到我们的下载按钮(同时也要设置一个参数, 用来告诉他的代理是谁被点击了) , 利用方法监听他的点击事件:
- (IBAction)downloadClick:(UIButton *)btn;
/**
* 点击了下载按钮
*/
- (IBAction)downloadClick:(UIButton *)btn {
// 1.让按钮失效
self.app.downloaded = YES;
btn.enabled = NO;
// 2.通知代理
if ([self.delegate respondsToSelector:@selector(appCellDidClickedDownloadBtn:)]) {
[self.delegate appCellDidClickedDownloadBtn:self];
}
}
- 然后让我们的控制器成为我们的代理
* 遵守协议: <GJAppCellDelegate>
*实现方法:
#pragma mark - cell的代理方法
- (void)appCellDidClickedDownloadBtn:(MJAppCell *)cell
{
// 1.添加标签
UILabel *label = [[UILabel alloc] init];
label.text = [NSString stringWithFormat:@"成功下载%@", cell.app.name];
label.font = [UIFont systemFontOfSize:12];
label.textAlignment = NSTextAlignmentCenter;
label.textColor = [UIColor whiteColor];
label.backgroundColor = [UIColor blackColor];
label.frame = CGRectMake(0, 0, 150, 25);
label.center = CGPointMake(160, 240);
label.alpha = 0.0;
label.layer.cornerRadius = 5;
label.clipsToBounds = YES;
[self.view addSubview:label];
// 2.动画
[UIView animateWithDuration:0.5 animations:^{
label.alpha = 0.5;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.5 delay:0.5 options:UIViewAnimationOptionCurveLinear animations:^{
label.alpha = 0.0;
} completion:^(BOOL finished) {
[label removeFromSuperview];
}];
}];
}
但是, 注意的是, 一旦我们让现在按钮失效, 在后面的当我们的cell从缓存池去的时候, 我们的cell中的下载按钮还是失效的, 所以我们的解决方法就是在重写cell的setter方法里面加上这么一句代码:
// 覆盖按钮的状态
self.downloadView.enabled = (self.app.isDownloaded == NO);
至于这个应用的数组转模型, 数据的加载等等我就不写了, 和我们以前的代码一样的