zoukankan      html  css  js  c++  java
  • 一个java程序员自学IOS开发之路(五)

    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的两种样式

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

    UITableViewDataSource协议

     

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

     

    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模型: 存放数据模型所有子控件的framecell的高度 

     

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

    }

  • 相关阅读:
    Linux服务器使用tar加密压缩文件
    ssh-copy-id使用非默认22端口
    Nginx日志分割脚本
    MySQL的yum源
    vSphere Client开启虚拟机提示:出现了常规系统错误: 由于目标计算机积极拒绝,无法连接。
    ESXi主机遗忘密码重置密码
    扩容swap交换分区空间
    ESXi上的固态硬盘识别为非SSD
    VMware Vcenter Server 6.0忘记密码
    Centos6与Centos7区别
  • 原文地址:https://www.cnblogs.com/yu3-/p/4928205.html
Copyright © 2011-2022 走看看