2015/10/24
Day 23
我插入移动硬盘后,电脑右上角老是出现一个齿轮转啊转,然后弹出对话框说有新文件加入电脑什么文件夹,要不要去看,关还关不掉,于是乎,昨晚脑袋一抽就把那个弹出来的文件夹移入废纸篓,然后发现不能这么干,就从废纸篓恢复,然而Finder就卡死了= =不管怎样都没响应,我烦的不行就强制按电源键重启电脑了(用Win系统的坏习惯,有事没事重启一下),然后!!!输入密码登陆,如下图:
然后就重启了= =,无限循环进不了系统,各种方法无果后,没办法去了售后店。解决方法,重装系统,花费300大洋,我去!真特么贵,唉~谁叫自己作呢,弄好后回家第一件事就是自制U盘启动盘。
其实很简单,先下好镜像文件,然后插入U盘,格式化它,再可以给它分区,这个随意我就没分,最后终端输入命令,等着就行了。
命令如下
安装包名称:Install OS X El Capitan.app
制作U盘启动盘指令
sudo /Applications/Install\ OS\ X\ El\ Capitan.app/Contents/Resources/createinstallmedia --volume /Volumes/u盘 --applicationpath /Applications/Install\ OS\ X\ El\ Capitan.app --nointeraction
第二件事就是开启TimeMachine备份。
之前写的代码都没了= =,唉,不说这事了,都是泪%>_<%
2015/10/25
Day 24
调试新系统,调整心情,再一次安装Xcode,继续学习啦~
今天开始学习UITableView
UITableView
在众多app中,能看到各式各样的表格数据,如下
在iOS中,要实现表格数据展示,最常用的做法就是使用UITableView
UITableView继承自UIScrollView,因此支持垂直滚动,而且性能极佳
UITableView的两种样式
- UITableView需要一个数据源(dataSource)来显示数据
- UITableView会向数据源查询一共有多少行数据以及每一行显示什么数据等
- 没有设置数据源的UITableView只是个空壳
- 凡是遵守UITableViewDataSource协议的OC对象,都可以是UITableView的数据源
UITableViewDataSource协议
Cell简介
UITableView的每一行都是一个UITableViewCell,通过dataSource的tableView:cellForRowAtIndexPath:方法来初始化每一行
UITableViewCell内部有个默认的子视图:contentView,contentView是UITableViewCell所显示内容的父视图,可显示一些辅助指示视图。还可以通过cell的accessoryView属性来自定义辅助指示视图(比如往右边放一个开关)
UITableViewCell的contentView
contentView下默认有3个子视图
其中2个是UILabel(通过UITableViewCell的textLabel和detailTextLabel属性访问)
第3个是UIImageView(通过UITableViewCell的imageView属性访问)
UITableViewCell还有一个UITableViewCellStyle属性,用于决定使用contentView的哪些子视图,以及这些子视图在contentView中的位置
Cell的重用
- iOS设备的内存有限,如果用UITableView显示成千上万条数据,就需要成千上万个UITableViewCell对象的话,那将会耗尽iOS设备的内存。要解决该问题,需要重用UITableViewCell对象
- 重用原理:当滚动列表时,部分UITableViewCell会移出窗口,UITableView会将窗口外的UITableViewCell放入一个对象池中,等待重用。当UITableView要求dataSource返回UITableViewCell时,dataSource会先查看这个对象池,如果池中有未使用的UITableViewCell,dataSource会用新的数据配置这个UITableViewCell,然后返回给UITableView,重新显示到窗口中,从而避免创建新对象
- 还有一个非常重要的问题:有时候需要自定义UITableViewCell(用一个子类继承UITableViewCell),而且每一行用的不一定是同一种UITableViewCell,所以一个UITableView可能拥有不同类型的UITableViewCell,对象池中也会有很多不同类型的UITableViewCell,那么UITableView在重用UITableViewCell时可能会得到错误类型的UITableViewCell
- 解决方案:UITableViewCell有个NSString *reuseIdentifier属性,可以在初始化UITableViewCell的时候传入一个特定的字符串标识来设置reuseIdentifier(一般用UITableViewCell的类名)。当UITableView要求dataSource返回UITableViewCell时,先通过一个字符串标识到对象池中查找对应类型的UITableViewCell对象,如果有,就重用,如果没有,就传入这个字符串标识来初始化一个UITableViewCell对象
Cell的重用代码
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1.定义一个cell的标识
static NSString *yu3 = @“yu3”;
// 2.从缓存池中取出cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:yu3];
// 3.如果缓存池中没有cell
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:yu3];
}
// 4.设置cell的属性...
return cell;
}
上面的写法并不好,因为数据源不需要知道cell内部的标识为什么,cell的标识cell自己最清楚,应该把创建cell的代码封装在cell里面,如下(YUCell为我自定义的cell类名)
+ (instancetype)cellWithTableView:(UITableView *)tableView{
static NSString *yu3 = @“yu3Cell";
YUCell *cell = [tableView dequeueReusableCellWithIdentifier:yu3];
if (cell == nil) {
cell = [[YUCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:yu3];
}
return cell;
}
这样,数据源里的方法就很简单
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//创建cell
YUCell *cell = [YUCell cellWithTableView:tableView];
//设置cell的属性...
return cell;
}
这样的话,如果以后cell里面的东西要变化,只需要动cell里的代码,数据源里代码不需要改变。
2015/10/26
Day 25
今天做了一个UIScrollView和UITableView综合的小项目
仿造团购app的界面,上面的广告可以自动翻页,滑到下面有按钮点击后加载数据
上面的广告,每个cell还有下面的加载更多按钮都是用xib封装的
使用xib封装一个view的步骤:
1.新建一个xib文件描述一个view的内部结构
2.新建一个自定义的类(自定义类需要继承自系统自带的view, 继承自哪个类, 取决于xib根对象的Class)
3.新建类的类名最好跟xib的文件名保持一致
4.将xib中的控件 和 自定义类的.m文件 进行连线
5.提供一个类方法返回一个创建好的自定义view(屏蔽从xib加载的过程)
6.提供一个模型属性让外界传递模型数据
7.重写模型属性的setter方法,在这里将模型数据展示到对应的子控件上面
代理的使用
像上面的加载更多按钮,他是在xib里面的,如果想让用户点击它加载新数据,这件事得控制器ViewController来做。可以用代理模式实现,让ViewController成为YUTgFooterView(我封装该View的类名)的代理(delegate),这样ViewController就可以监听那个按钮点击做出反应了。
使用delegate的步骤
- 先搞清楚谁是谁的代理(delegate)
- 定义代理协议,协议名称的命名规范:控件类名 + Delegate
- 定义代理方法
- 代理方法一般都定义为@optional
- 代理方法名都以控件名开头
- 代理方法至少有1个参数,将控件本身传递出去
- 设置代理(delegate)对象 (比如myView.delegate = xxxx;)
- 代理对象遵守协议
- 代理对象实现协议里面该实现的方法
- 在恰当的时刻调用代理对象(delegate)的代理方法,通知代理发生了什么事情(在调用之前判断代理是否实现了该代理方法)
2015/10/27
Day 26
昨天的项目中,cell是xib封装的,每个cell的内容都是类似的,高度也是一样的,这样太局限,比如微博页面,它的每个cell的内容是不一样的,这就需要用代码来自定义cell
如图,每个cell最多可以有一个头像,一个名字,一个vip标识,一个文本,一张图片,plist文件结构如下
首先,把plist封装成模型
创建自定义的cell类继承自UItableViewCell
cell内部的控件属性要写在.m文件中的类扩展中
实现cell的构造方法
/**
* 构造方法(在初始化对象的时候会调用)
* 一般在这个方法中添加需要显示的子控件
*/
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
UIImageView *iconView = [[UIImageView alloc] init];
[self.contentView addSubview:iconView];
self.iconView = iconView;
UILabel *nameView = [[UILabel alloc] init];
nameView.font = YUNameFont;
nameView.backgroundColor = [UIColor orangeColor];
[self.contentView addSubview:nameView];
self.nameView = nameView;
UIImageView *vipView = [[UIImageView alloc] init];
[self.contentView addSubview:vipView];
self.vipView = vipView;
UILabel *textView = [[UILabel alloc] init];
textView.font = YUTextFont;
textView.numberOfLines = 0;//自动换行!
textView.backgroundColor = [UIColor blueColor];
[self.contentView addSubview:textView];
self.textView = textView;
UIImageView *pictureView = [[UIImageView alloc] init];
[self.contentView addSubview:pictureView];
self.pictureView = pictureView;
}
return self;
}
再重写成员属性的setter方法,里面设置控件数据
/**设置子控件的大小和数据 */
-(void)setStatus:(YUStatus *)status {
_status = status;
//设置数据
[self setData];
//设置控件大小
[self setFrame];
}
设置的代码冗长,我把他们封装了
别忘了实现.h中声明的类方法
+ (instancetype)cellWithTableView:(UITableView *)tableView{
static NSString *yu3 = @"statusCell";
YUStatusCell *cell = [tableView dequeueReusableCellWithIdentifier:yu3];
if (cell == nil) {
cell = [[YUStatusCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:yu3];
}
return cell;
}
最后到控制器,先让控制器继承自UITableViewController
设置成员属性
重写getter方法加载数据
- (NSArray *)statuses {
NSMutableArray *result = [[NSMutableArray alloc] init];
NSString *path = [[NSBundle mainBundle] pathForResource:@"statuses.plist" ofType:nil];
NSArray *dics = [NSArray arrayWithContentsOfFile:path];
for (NSDictionary *dic in dics) {
YUStatus *status = [YUStatus statusWithDic:dic];
[result addObject:status];
}
return result;
}
最后重写数据源方法
//默认就是1,可以不写这个方法
//- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// return 1;
//}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.statuses.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
YUStatusCell *cell = [YUStatusCell cellWithTableView:tableView];
cell.status = self.statuses[indexPath.row];
return cell;
}
这样运行出来是这个效果,很坑爹,因为个cell默认的高度是一样的,有个方法可以改变
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
//如何自定义cell高度?
return 100;
}
这里插播一下,计算一个字符串所占的size,如图我文本的背景色
第一种方法,利用控件的一个方法
//根据内部文字以及参数中给的参考size计算自适应的size
CGSize nameSize = [self.nameView sizeThatFits:CGSizeZero];
第二种方法,利用NSString一个类别(分类)中的方法
//根据内部文字以及参数中给的参考的最大size计算自适应的size
CGSize nameSize = [status.name boundingRectWithSize:CGSizeMake(MAXFLOAT, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:14]} context:nil].size;
第一个参数:参考的最大size,这里可以规定最大宽度和高度
第二个参数:一般写NSStringDrawingUsesLineFragmentOrigin |NSStringDrawingUsesFontLeading
第三个参数:是个字典,传入字体大小
那么问题来了,如何计算每个cell的高度?
cell的高度是根据里面的数据决定的,数据是由模型传过来的,因此cell的高度应该由模型来计算!
解决方法:
1.提供2个模型
- 数据模型: 存放文字数据\图片数据
- frame模型: 存放数据模型\所有子控件的frame\cell的高度
2.cell拥有一个frame模型(frame模型已经包含数据模型了)
3.重写frame模型属性的setter方法: 在这个方法中设置子控件的显示数据和frame
- (void)setStatus:(YUStatus *)status {
_status = status;
CGFloat padding = 20;
CGFloat iconX = padding;
CGFloat iconY = padding;
CGFloat iconW = 30;
CGFloat iconH = 30;
_iconF = CGRectMake(iconX, iconY, iconW, iconH);
//根据内部文字以及参数中给的参考的最大size计算自适应的size
CGSize nameSize = [status.name boundingRectWithSize:CGSizeMake(MAXFLOAT, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : YUNameFont} context:nil].size;
CGFloat nameX = CGRectGetMaxX(_iconF) + padding;
CGFloat nameY = iconY + (iconH - nameSize.height) / 2;
_nameF = CGRectMake(nameX, nameY, nameSize.width, nameSize.height);
CGFloat vipX = nameX + nameSize.width + padding;
CGFloat vipY = nameY;
CGFloat vipW = 14;
CGFloat vipH = 14;
_vipF = CGRectMake(vipX, vipY, vipW, vipH);
CGFloat textX = iconX;
CGFloat textY = CGRectGetMaxY(_iconF) + padding;
CGSize textSize = [status.text boundingRectWithSize:CGSizeMake(345, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName : YUTextFont} context:nil].size;
_textF = CGRectMake(textX, textY, textSize.width, textSize.height);
if (status.picture) {
CGFloat picX = textX;
CGFloat picY = CGRectGetMaxY(_textF) + padding;
CGFloat picW = 200;
CGFloat picH = 200;
_picF = CGRectMake(picX, picY, picW, picH);
_cellHight = CGRectGetMaxY(_picF) + padding;
} else {
_cellHight = CGRectGetMaxY(_textF) + padding;
}
}
4.frame模型数据的初始化已经采取懒加载的方式(每一个cell对应的frame模型数据只加载一次)
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
YUStatusCellFrame *frame = self.statusFrames[indexPath.row];
return frame.cellHight;
}
最后这样返回每个cell的高度就可以啦
最后说一下弹窗事件,我做了个点击cell弹出对话框修改用户名的功能
在iOS8以前,弹窗需要遵守协议还有实现按钮的点击处理,比较麻烦,而iOS8以后可以在一个方法里搞定,而且不用遵守协议
// cell点击事件监听
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
YUStatusCellFrame *statusFrame = self.statusFrames[indexPath.row];
NSString *name = statusFrame.status.name;
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"博主" message:nil preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.text = name;
}];
UIAlertAction *modify = [UIAlertAction actionWithTitle:@"修改" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {
statusFrame.status.name = alert.textFields[0].text;
//修改后计算出新的name所占frame的大小
CGSize newSize = [statusFrame.status.name boundingRectWithSize:CGSizeMake(MAXFLOAT, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : YUTextFont} context:nil].size;
statusFrame.nameF = CGRectMake(CGRectGetMinX(statusFrame.nameF) , CGRectGetMinY(statusFrame.nameF), newSize.width, newSize.height);
//重新加载该行数据
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight];
}];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
}];
[alert addAction:modify];
[alert addAction:cancel];
[self presentViewController:alert animated:YES completion:nil];
}