我插入移动硬盘后,电脑右上角老是出现一个齿轮转啊转,然后弹出对话框说有新文件加入电脑什么文件夹,要不要去看,关还关不掉,于是乎,昨晚脑袋一抽就把那个弹出来的文件夹移入废纸篓,然后发现不能这么干,就从废纸篓恢复,然而 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 备份。
之前写的代码都没了 = =,唉,不说这事了,都是泪 %>_<%
调试新系统,调整心情,再一次安装 Xcode,继续学习啦 ~
今天开始学习UITableView
在众多app中,能看到各式各样的表格数据,如下
在 iOS中,要实现表格数据展示,最常用的做法就是使用 UITableView
UITableView 继承自UIScrollView ,因此支持垂直滚动,而且性能极佳
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 的重用
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里的代码,数据源里代码不需要改变。
今天做了一个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 的步骤
昨天的项目中, 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个模型
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];
}