转载

一个java程序员自学IOS开发之路(五)

2015/10/24

Day 23

我插入移动硬盘后,电脑右上角老是出现一个齿轮转啊转,然后弹出对话框说有新文件加入电脑什么文件夹,要不要去看,关还关不掉,于是乎,昨晚脑袋一抽就把那个弹出来的文件夹移入废纸篓,然后发现不能这么干,就从废纸篓恢复,然而 Finder就卡死了 = =不管怎样都没响应,我烦的不行就强制按电源键重启电脑了(用 Win系统的坏习惯,有事没事重启一下),然后!!!输入密码登陆,如下图:

一个java程序员自学IOS开发之路(五)

然后就重启了 = =,无限循环进不了系统,各种方法无果后,没办法去了售后店。解决方法,重装系统,花费 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中,能看到各式各样的表格数据,如下

一个java程序员自学IOS开发之路(五)

iOS中,要实现表格数据展示,最常用的做法就是使用 UITableView

UITableView 继承自UIScrollView ,因此支持垂直滚动,而且性能极佳

UITableView 的两种样式

一个java程序员自学IOS开发之路(五)

  1. UITableView 需要一个数据源(dataSource) 来显示数据
  2. UITableView会向数据源查询一共有多少行数据以及每一行显示什么数据等
  3. 没有设置数据源的UITableView 只是个空壳
  4. 凡是遵守UITableViewDataSource 协议的OC 对象,都可以是UITableView 的数据源

UITableViewDataSource 协议

一个java程序员自学IOS开发之路(五)

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 中的位置

一个java程序员自学IOS开发之路(五)

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 综合的小项目

一个java程序员自学IOS开发之路(五) 一个java程序员自学IOS开发之路(五)

仿造团购app的界面,上面的广告可以自动翻页, 滑到下面有按钮点击后加载数据

上面的广告,每个cell还有下面的加载更多按钮都是用xib封装的

一个java程序员自学IOS开发之路(五)

一个java程序员自学IOS开发之路(五)

一个java程序员自学IOS开发之路(五)

使用 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个参数 ,将控件本身传递出去

一个java程序员自学IOS开发之路(五)

  • 设置代理(delegate) 对象  ( 比如myView.delegate = xxxx;)

一个java程序员自学IOS开发之路(五)

  • 代理对象遵守协议

一个java程序员自学IOS开发之路(五)

  • 代理对象实现协议里面该实现的方法

一个java程序员自学IOS开发之路(五)

  • 在恰当的时刻调用代理对象 (delegate)的代理方法 ,通知代理发生了什么事情 (在调用之前判断代理是否实现了该代理方法 )

一个java程序员自学IOS开发之路(五)

2015/10/27

Day 26

昨天的项目中, cell是 xib封装的,每个 cell的内容都是类似的,高度也是一样的,这样太局限,比如微博页面,它的每个 cell的内容是不一样的,这就需要用代码来自定义 cell 

如图,每个 cell最多可以有一个头像,一个名字,一个 vip标识,一个文本,一张图片, plist文件结构如下  

一个java程序员自学IOS开发之路(五) 一个java程序员自学IOS开发之路(五)

首先,把 plist封装成模型

一个java程序员自学IOS开发之路(五)

一个java程序员自学IOS开发之路(五)

创建自定义的cell 类继承自UItableViewCell 

一个java程序员自学IOS开发之路(五)

cell内部的控件属性要写在 .m文件中的类扩展中  

一个java程序员自学IOS开发之路(五)

实现 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 

一个java程序员自学IOS开发之路(五)

设置成员属性

一个java程序员自学IOS开发之路(五)

重写 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默认的高度是一样的,有个方法可以改变  

一个java程序员自学IOS开发之路(五)

- (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的高度  

一个java程序员自学IOS开发之路(五)

2.cell 拥有一个frame 模型(frame 模型已经包含数据模型了)

一个java程序员自学IOS开发之路(五)

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的高度就可以啦

一个java程序员自学IOS开发之路(五)

最后说一下弹窗事件 ,我做了个点击 cell弹出对话框修改用户名的功能

一个java程序员自学IOS开发之路(五)

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];

}

正文到此结束
Loading...