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

时间:2023-02-12 20:21:16

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属性来自定义辅助指示视图(比如往右边放一个开关)

UITableViewCellcontentView

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

}