IBAction:
1> 能保证方法可以连线
2> 相当于void
IBOutlet:
1> 能保证属性可以连线
下面列举一些在开发中可能用得上的UI控件
- UIButton 按钮
- UILabel 文本标签
- UITextField 文本输入框
- UIImageView 图片显示
- UIProgressView 进度条
- UISlider 滑块
- UISwitch 开关
- UISegmentControl 选项卡
- UIActivityIndicator 圈圈
- UIAlertView 对话框(中间弹框)
- UIActionSheet 底部弹框
- UIScrollView 滚动的控件
- UIPageControl 分页控件
- UITextView 能滚动的文字显示控件
- UITableView 表格
- UICollectionView 九宫格
- UIPickerView 选择器
- UIDatePicker 日期选择器
- UIWebView 网页显示控件
- UIToolbar 工具条
- UINavigationBar导航条
如何修改控件状态
那如何去修改控件的状态呢?方法很简单
- 每一个UI控件都是一个对象
- 修改UI控件的状态,其实就是修改控件对象的属性
- 比如修改UILabel显示的文字,就修改UILabel对象的text属性即可
- 比如修改UIImageView显示的图片,就修改UIImageView对象的image属性即可
不难想到,每一个UI控件肯定都有很多属性,比如:
- UIProgressView进度条控件有progress属性(进度值)
- UILabel和UITextField都有text属性(显示文字)
- … …
虽然,每一个UI控件都有自己的独特属性,但是有些属性是每个UI控件都具备的,比如每一个UI控件都有自己的位置和尺寸、都有自己的父控件、子控件。于是,所有的UI控件最终都继承自UIView,UI控件的公共属性都定义在UIView中,比如:
- frame :位置和尺寸
- center :中心点位置
- … …
UIView的常见属性
@property(nonatomic,readonly) UIView *superview;
获得自己的父控件对象
@property(nonatomic,readonly,copy) NSArray *subviews;
获得自己的所有子控件对象
@property(nonatomic) NSInteger tag;
控件的ID\标识,父控件可以通过tag来找到对应的子控件
@property(nonatomic) CGAffineTransform transform;
控件的形变属性(可以设置旋转角度、比例缩放、平移等属性)
@property(nonatomic) CGRect frame;
控件所在矩形框在父控件中的位置和尺寸(以父控件的左上角为坐标原点)
@property(nonatomic) CGRect bounds;
控件所在矩形框的位置和尺寸(以自己左上角为坐标原点,所以bounds的x\y一般为0)
@property(nonatomic) CGPoint center;
- 控件中点的位置(以父控件的左上角为坐标原点)
UIView的常见方法
- (void)addSubview:(UIView *)view;
添加一个子控件view- (void)removeFromSuperview;
从父控件中移除- (UIView *)viewWithTag:(NSInteger)tag;
根据一个tag标识找出对应的控件(一般都是子控件)
代码创建按钮
在开发过程中,并不是每次都通过storyboard拖控件完成UI界面,因为storyboard上面的界面是“固定死”的,有时候可能会在程序运行过程中动态地添加一些新的控件到界面上
比如QQ的聊天信息,是有人发出一条信息后才动态显示出来的。因此,需要掌握如何用代码动态地添加控件
实际上,storyboard的本质就是根据图形界面描述转成相应的代码
下面演示用代码创建按钮
1 | // 1.创建一个自定义的按钮 |
UIButton的状态
normal(普通状态)
- 默认情况(Default)
- 对应的枚举常量:UIControlStateNormal
highlighted(高亮状态)
- 按钮被按下去的时候(手指还未松开)
- 对应的枚举常量:UIControlStateHighlighted
disabled(失效状态,不可用状态)
- 如果enabled属性为NO,就是处于disable状态,代表按钮不可以被点击
- 对应的枚举常量:UIControlStateDisabled
实现简单动画
在iOS开发中,想实现一些小动画是非常容易的
- 系统会根据某个属性值的改变自动形成动画
- 比如x值本来是10,然后x值突然改为了100,系统会通过平移动画的方式让x值慢慢从10变到100
简易动画大致有2种方式:
- 头尾式
1 | [UIView beginAnimations:nil context:nil]; |
- Block式
1 | [UIView animateWithDuration:0.5 animations:^{ |
修改控件的位置和尺寸
通过以下属性可以修改控件的位置
- frame.origin
- center
通过以下属性可以修改控件的尺寸
- frame.size
- bounds.size
transform属性
利用transform属性可以修改控件的位移(位置)、缩放、旋转
创建一个transform属性
1 | CGAffineTransform CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty) ; |
在某个transform的基础上进行叠加
1 | CGAffineTransform CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty); |
清空之前设置的transform属性
1 | view.transform = CGAffineTransformIdentity; |
常见类型
一个UIColor代表一种颜色,通过UIColor的类方法,可以获得很多常用的颜色
1 | + (UIColor *)blackColor; // 0.0 white 黑色 |
在用代码创建按钮的同时指定按钮样式
1 | UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom]; |
UIButton和UIImageView
相同点:都能显示图片
不同点
- UIButton默认情况就能监听点击事件,而UIImageView默认情况下不能
- UIButton可以在不同状态下显示不同的图片
- UIButton既能显示文字,又能显示图片
如何选择
- UIButton:需要显示图片,点击图片后需要做一些特定的操作
- UIImageView:仅仅需要显示图片,点击图片后不需要做任何事情
NSArray和NSDictionary的使用
当图片内容非常多时,“根据index来设置内容”的代码就不具备扩展性,要经常改动
为了改变现状,可以考虑讲图片数据线保存到一个数组中,数组中有序地放着很多字典,一个字典代表一张图片数据,包含了图片名、图片描述
1 | @property (strong, nonatomic) NSArray *images; |
由于只需要初始化一次图片数据,因此放在get方法中初始化
将属性放在get方法中初始化的方式,称为“懒加载”\”延迟加载”
什么是Plist文件
直接将数据直接写在代码里面,不是一种合理的做法。如果数据经常改,就要经常翻开对应的代码进行修改,造成代码扩展性低
因此,可以考虑将经常变的数据放在文件中进行存储,程序启动后从文件中读取最新的数据。如果要变动数据,直接修改数据文件即可,不用修改代码
一般可以使用属性列表文件存储NSArray或者NSDictionary之类的数据,这种属性列表文件的扩展名是plist,因此也成为“Plist文件”
解析Plist文件
接下来通过代码来解析Plist文件中的数据
- 获得Plist文件的全路径
1 | NSBundle *bundle = [NSBundle mainBundle]; |
- 加载plist文件
1 | _images = [NSArray arrayWithContentsOfFile:path]; |
NSBundle
1> 一个NSBundle代表一个文件夹,利用NSBundle能访问对应的文件夹
2> 利用mainBundle就可以访问软件资源包中的任何资源
3> 模拟器应用程序的安装路径
汤姆猫
连续播放多张图片
1 |
|
文档注释的写法:
1 | /** 这是一只显示图片的猫 */ |
这样写可以在调用的时候显示注释
苹果API常用英语名词
- indicating 决定
- in order to 以便
- rectangle bounds 矩形尺寸
- applied 应用
- entirety 全部
- technique 方法
- truncating 截短
- wrapping 换行
- string 字符串
- familiar style 简体
- The styled text 主题样式
- Constants 常量
- Attribute 属性
- Consecutive 连续
- Shrink 收缩
- Discussion 详述
- Rendering 渲染
- Pasting 粘贴
- Prohibits 禁止
- Albeit 虽然
- Particular 特殊的,详细的
- Specify 指定
- Times 次数
- In that order 在这个命令下
- Passing 传递
- Determines 决定
- Resize 调整大小
- Comprises 包含
- Positive 正数
- Negative 负数
- Reverse 反转
- Valid 有效的
- Configure 设定
- Instead 相反
- Primarily 主要
- Obvious 非常
- Divergence 区别
- Conceptual 概念
- The model-view-controller design pattern MVC
- clearly 明显
- encapsulate 封装
- transparent 透明
- opaque 不透明
- momentary 瞬间
- diagonal 斜线的
- lifts their finger 抬起手指
- rest 停止
- reckoning 估算
- countDown 倒计时
- evenly 均匀的
- designation 名称
- trim 裁剪
- simulate 模拟
- incompatible 不匹配
用模型取代字典的好处
使用字典的坏处
- 一般情况下,设置数据和取出数据都使用“字符串类型的key”,编写这些key时,编译器不会有任何友善提示,需要手敲
1 |
|
- 手敲字符串key,key容易写错
- Key如果写错了,编译器不会有任何警告和报错,造成设错数据或者取错数据
使用模型的好处
- 所谓模型,其实就是数据模型,专门用来存放数据的对象,用它来表示数据会更加专业
- 模型设置数据和取出数据都是通过它的属性,属性名如果写错了,编译器会马上报错,因此,保证了数据的正确性
- 使用模型访问属性时,编译器会提供一系列的提示,提高编码效率
1 | app.name = @"Jack”; |
字典转模型
字典转模型的过程最好封装在模型内部
模型应该提供一个可以传入字典参数的构造方法
1 | - (instancetype)initWithDict:(NSDictionary *)dict; |
1 | - (void)setValue:(id)value forUndefinedKey:(nonnull NSString *)key |
1 | for (NSDictionary *dict in dataDict) { |
instancetype
instancetype在类型表示上,跟id一样,可以表示任何对象类型
instancetype只能用在返回值类型上,不能像id一样用在参数类型上
instancetype比id多一个好处:编译器会检测instancetype的真实类型
Xib文件的使用
Xib文件可以用来描述某一块局部的UI界面
Xib文件的加载
- 方法1
1 | NSArray *objs = [[NSBundle mainBundle] loadNibNamed:@"MJAppView" owner:nil options:nil]; |
这个方法会创建xib中的所有对象,并且将对象按顺序放到objs数组中
(如果xib如右图所示,那么objs数组中依次会有3个对象:1个UIView、1个UIButton、1个UISwitch)
- 方法2
bundle参数可以为nil,默认就是main bundle
1 | UINib *nib = [UINib nibWithNibName:@"MJAppView" bundle:[NSBundle mainBundle]]; |
- 在开发阶段,面向开发者的是xib文件; 当把应用装到手机上时,xib文件就会转为nib文件
UIScrollView
移动设备的屏幕大小是极其有限的,因此直接展示在用户眼前的内容也相当有限
当展示的内容较多,超出一个屏幕时,用户可通过滚动手势来查看屏幕以外的内容
普通的UIView不具备滚动功能,不能显示过多的内容
UIScrollView是一个能够滚动的视图控件,可以用来展示大量的内容,并且可以通过滚动查看所有的内容
举例:手机上的“设置”、其他示例程序
UIScrollView的基本使用
UIScrollView的用法很简单
- 将需要展示的内容添加到UIScrollView中
- 设置UIScrollView的contentSize属性,告诉UIScrollView所有内容的尺寸,也就是告诉它滚动的范围(能滚多远,滚到哪里是尽头)
UIScrollView无法滚动的解决办法
如果UIScrollView无法滚动,可能是以下原因:
- 没有设置contentSize
- scrollEnabled = NO
- 没有接收到触摸事件:userInteractionEnabled = NO
- 没有取消autolayout功能(要想scrollView滚动,必须取消autolayout)
UIScrollView和控制器
一般情况下,就设置UIScrollView所在的控制器 为 UIScrollView的delegate
设置控制器为UIScrollView的delegate有2种方法:
- 通过代码(self就是控制器)
1 | self.scrollView.delegate = self; |
- 通过storyboard拖线(右击UIScrollView)
UIScrollView的常见属性
1 | @property(nonatomic) CGPoint contentOffset; |
这个属性用来表示UIScrollView滚动的位置
1 | @property(nonatomic) CGSize contentSize; |
这个属性用来表示UIScrollView内容的尺寸,滚动范围(能滚多远)
1 | @property(nonatomic) UIEdgeInsets contentInset; |
这个属性能够在UIScrollView的4周增加额外的滚动区域
UIScrollView的代理(delegate)
- 很多时候,我们想在 UIScrollView 正在滚动 或 滚动到某个位置 或者 停止滚动 时做一些特定的操作
- 要想完成上述功能,前提条件就是能够监听到 UIScrollView 的整个滚动过程
- 当 UIScrollView 发生一系列的滚动操作时, 会自动通知它的代理(delegate)对象,给它的代理发送相应的消息,让代理得知它的滚动情况
- 也就是说,要想监听 UIScrollView 的滚动过程,就必须先给 UIScrollView 设置一个代理对象,然后通过代理得知 UIScrollView 的滚动过程
1 | // 用户开始拖拽时调用 |
内容缩放
UIScrollView不仅能滚动显示大量内容,还能对其内容进行缩放处理
也就是说,要完成缩放功能的话,只需要将需要缩放的内容添加到UIScrollView中
UIScrollView的缩放原理
当用户在UIScrollView身上使用捏合手势时,UIScrollView会给代理发送一条消息,询问代理究竟要缩放自己内部的哪一个子控件(哪一块内容)
1 | // 用户使用捏合手势时调用 |
当用户在 UIScrollView 身上使用捏合手势时,UIScrollView 会调用代理的 viewForZoomingInScrollView:
方法,这个方法返回的控件就是需要进行缩放的控件
缩放实现步骤
- 设置UIScrollView的id
delegate代理对象 - 设置minimumZoomScale :缩小的最小比例
- 设置maximumZoomScale :放大的最大比例
- 让代理对象实现下面的方法,返回需要缩放的视图控件
1 | - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView; |
跟缩放相关的其他代理方法
- 缩放完毕的时候调用
1 | - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view |
- 正在缩放的时候调用
1 | - (void)scrollViewDidZoom:(UIScrollView *)scrollView |
分页
只要将 UIScrollView的pageEnabled 属性设置为 YES ,UIScrollView 会被分割成多个独立页面,里面的内容就能进行分页展示
一般会配合 UIPageControl 增强分页效果,UIPageControl 常用属性如下
- 一共有多少页
1 | @property(nonatomic) NSInteger numberOfPages; |
- 当前显示的页码
1 | @property(nonatomic) NSInteger currentPage; |
- 只有一页时,是否需要隐藏页码指示器
1 | @property(nonatomic) BOOL hidesForSinglePage; |
- 其他页码指示器的颜色
1 | @property(nonatomic,retain) UIColor *pageIndicatorTintColor; |
- 当前页码指示器的颜色
1 | @property(nonatomic,retain) UIColor *currentPageIndicatorTintColor; |
NSTimer
NSTimer叫做“定时器”,它的作用如下
- 在指定的时间执行指定的任务
- 每隔一段时间执行指定的任务
调用下面的方法就会开启一个定时任务
1 | + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti |
每隔ti秒,调用一次aTarget的aSelector方法,yesOrNo决定了是否重复执行这个任务
通过invalidate方法可以停止定时器的工作,一旦定时器被停止了,就不能再次执行任务。只能再创建一个新的定时器才能执行新的任务
1 | - (void)invalidate; |
UITableView
在众多移动应用中,能看到各式各样的表格数据
在iOS中,要实现表格数据展示,最常用的做法就是使用UITableView
UITableView继承自UIScrollView,因此支持垂直滚动,而且性能极佳
如何展示数据
UITableView需要一个数据源(dataSource)来显示数据
UITableView会向数据源查询一共有多少行数据以及每一行显示什么数据等
没有设置数据源的UITableView只是个空壳
凡是遵守UITableViewDataSource协议的OC对象,都可以是UITableView的数据源
UITableViewDataSource协议
1 | // 一共有多少组数据 |
1 | @property (nonatomic, assign) id <UITableViewDataSource> dataSource; |
tableView展示数据的过程
调用数据源的下面方法得知一共有多少组数据
1 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; |
调用数据源的下面方法得知每一组有多少行数据
1 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section; |
调用数据源的下面方法得知每一行显示什么内容
1 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; |
Cell简介
UITableView的每一行都是一个UITableViewCell,通过dataSource的tableView:cellForRowAtIndexPath:方法来初始化每一行
UITableViewCell内部有个默认的子视图:contentView,contentView是UITableViewCell所显示内容的父视图,可显示一些辅助指示视图
辅助指示视图的作用是显示一个表示动作的图标,可以通过设置UITableViewCell的accessoryType来显示,默认是UITableViewCellAccessoryNone(不显示辅助指示视图),其他值如下:
还可以通过cell的accessoryView属性来自定义辅助指示视图(比如往右边放一个开关)
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的重用代码
1 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath |
使用xib封装一个view的步骤
- 新建一个xib文件描述一个view的内部结构(假设叫做MJTgCell.xib)
- 新建一个自定义的类(自定义类需要继承自系统自带的view, 继承自哪个类, 取决于xib根对象的Class)
- 新建类的类名最好跟xib的文件名保持一致(比如类名就叫做MJTgCell)
- 将xib中的控件 和 自定义类的.m文件 进行连线
- 提供一个类方法返回一个创建好的自定义view(屏蔽从xib加载的过程)
- 提供一个模型属性让外界传递模型数据
- 重写模型属性的setter方法,在这里将模型数据展示到对应的子控件上面
通过代码自定义cell(cell的高度不一致)
1.新建一个继承自UITableViewCell的类
2.重写initWithStyle:reuseIdentifier:方法
- 添加所有需要显示的子控件(不需要设置子控件的数据和frame, 子控件要添加到contentView中)
- 进行子控件一次性的属性设置(有些属性只需要设置一次, 比如字体\固定的图片)
3.提供2个模型
- 数据模型: 存放文字数据\图片数据
- frame模型: 存放数据模型\所有子控件的frame\cell的高度
4.cell拥有一个frame模型(不要直接拥有数据模型)
5.重写frame模型属性的setter方法: 在这个方法中设置子控件的显示数据和frame
6.frame模型数据的初始化已经采取懒加载的方式(每一个cell对应的frame模型数据只加载一次)
Delegate的使用场合
- 对象A内部发生了一些事情,想通知对象B
- 对象B想监听对象A内部发生了什么事情
- 对象A想在自己的方法内部调用对象B的某个方法,并且对象A不能对对象B有耦合依赖
- 对象A想传递数据给对象B
- ……
以上情况,结果都一样:对象B是对象A的代理(delegate)
使用delegate的步骤
先搞清楚谁是谁的代理(delegate)
定义代理协议,协议名称的命名规范:控件类名 + Delegate
定义代理方法
- 代理方法一般都定义为@optional
- 代理方法名都以控件名开头
- 代理方法至少有1个参数,将控件本身传递出去
设置代理(delegate)对象 (比如myView.delegate = xxxx;)
- 代理对象遵守协议
- 代理对象实现协议里面该实现的方法
在恰当的时刻调用代理对象(delegate)的代理方法,通知代理发生了什么事情
(在调用之前判断代理是否实现了该代理方法)
新博客文章地址:UIButton,UIScrollView,UITableView常见属性,使用案例
CSDN文章地址:UIButton,UIScrollView,UITableView常见属性,使用案例