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.创建一个自定义的按钮
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
// 2.添加按钮
[self.view addSubview:btn];
// 3.设置按钮的位置和尺寸
btn.frame = CGRectMake(100, 100, 100, 100);
// 4.监听按钮点击(点击按钮后就会调用self的btnClick方法)
[btn addTarget:self action:@selector(btnClick) forControlEvents:UIControlEventTouchUpInside];
// 5.设置按钮在默认状态下的属性
// 5.1.默认状态的背景
[btn setBackgroundImage:[UIImage imageNamed:@"btn_01"] forState:UIControlStateNormal];
// 5.2.默认状态的文字
[btn setTitle:@"点我啊" forState:UIControlStateNormal];
// 5.3.默认状态的文字颜色
[btn setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
// 6.设置按钮在高亮状态下的属性
// 6.1.高亮状态的背景
[btn setBackgroundImage:[UIImage imageNamed:@"btn_02"] forState:UIControlStateHighlighted];
// 6.2.高亮状态的文字
[btn setTitle:@"高亮显示" forState:UIControlStateHighlighted];
// 6.3.高亮状态的文字颜色
[btn setTitleColor:[UIColor blueColor] forState:UIControlStateHighlighted];
UIButton的状态
normal(普通状态)
- 默认情况(Default)
- 对应的枚举常量:UIControlStateNormal
highlighted(高亮状态)
- 按钮被按下去的时候(手指还未松开)
- 对应的枚举常量:UIControlStateHighlighted
disabled(失效状态,不可用状态)
- 如果enabled属性为NO,就是处于disable状态,代表按钮不可以被点击
- 对应的枚举常量:UIControlStateDisabled
实现简单动画
在iOS开发中,想实现一些小动画是非常容易的
- 系统会根据某个属性值的改变自动形成动画
- 比如x值本来是10,然后x值突然改为了100,系统会通过平移动画的方式让x值慢慢从10变到100
简易动画大致有2种方式:
- 头尾式
[UIView beginAnimations:nil context:nil];
/** 需要执行动画的代码 **/
[UIView commitAnimations];
- Block式
[UIView animateWithDuration:0.5 animations:^{
/** 需要执行动画的代码 **/
}];
修改控件的位置和尺寸
通过以下属性可以修改控件的位置
- frame.origin
- center
通过以下属性可以修改控件的尺寸
- frame.size
- bounds.size
transform属性
利用transform属性可以修改控件的位移(位置)、缩放、旋转
创建一个transform属性
CGAffineTransform CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty) ;
CGAffineTransform CGAffineTransformMakeScale(CGFloat sx, CGFloat sy);
CGAffineTransform CGAffineTransformMakeRotation(CGFloat angle)
// (angle是弧度制,并不是角度制)
在某个transform的基础上进行叠加
CGAffineTransform CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty);
CGAffineTransform CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy);
CGAffineTransform CGAffineTransformRotate(CGAffineTransform t, CGFloat angle);
清空之前设置的transform属性
view.transform = CGAffineTransformIdentity;
常见类型
一个UIColor代表一种颜色,通过UIColor的类方法,可以获得很多常用的颜色
+ (UIColor *)blackColor; // 0.0 white 黑色
+ (UIColor *)darkGrayColor; // 0.333 white 深灰色
+ (UIColor *)lightGrayColor; // 0.667 white 亮灰色
+ (UIColor *)whiteColor; // 1.0 white 白色
+ (UIColor *)grayColor; // 0.5 white 灰色
+ (UIColor *)redColor; // 1.0, 0.0, 0.0 RGB 红色
+ (UIColor *)greenColor; // 0.0, 1.0, 0.0 RGB 绿色
+ (UIColor *)blueColor; // 0.0, 0.0, 1.0 RGB 蓝色
+ (UIColor *)cyanColor; // 0.0, 1.0, 1.0 RGB 青色
+ (UIColor *)yellowColor; // 1.0, 1.0, 0.0 RGB 黄色
+ (UIColor *)magentaColor; // 1.0, 0.0, 1.0 RGB 品红
+ (UIColor *)orangeColor; // 1.0, 0.5, 0.0 RGB 橙色
+ (UIColor *)purpleColor; // 0.5, 0.0, 0.5 RGB 紫色
+ (UIColor *)brownColor; // 0.6, 0.4, 0.2 RGB 棕色
+ (UIColor *)clearColor; // 0.0 white, 0.0 alpha 清除颜色(空色)
在用代码创建按钮的同时指定按钮样式
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
UIButton和UIImageView
相同点:都能显示图片
不同点
- UIButton默认情况就能监听点击事件,而UIImageView默认情况下不能
- UIButton可以在不同状态下显示不同的图片
- UIButton既能显示文字,又能显示图片
如何选择
- UIButton:需要显示图片,点击图片后需要做一些特定的操作
- UIImageView:仅仅需要显示图片,点击图片后不需要做任何事情
NSArray和NSDictionary的使用
当图片内容非常多时,“根据index来设置内容”的代码就不具备扩展性,要经常改动
为了改变现状,可以考虑讲图片数据线保存到一个数组中,数组中有序地放着很多字典,一个字典代表一张图片数据,包含了图片名、图片描述
@property (strong, nonatomic) NSArray *images;
由于只需要初始化一次图片数据,因此放在get方法中初始化
将属性放在get方法中初始化的方式,称为“懒加载”\”延迟加载”
什么是Plist文件
直接将数据直接写在代码里面,不是一种合理的做法。如果数据经常改,就要经常翻开对应的代码进行修改,造成代码扩展性低
因此,可以考虑将经常变的数据放在文件中进行存储,程序启动后从文件中读取最新的数据。如果要变动数据,直接修改数据文件即可,不用修改代码
一般可以使用属性列表文件存储NSArray或者NSDictionary之类的数据,这种属性列表文件的扩展名是plist,因此也成为“Plist文件”
解析Plist文件
接下来通过代码来解析Plist文件中的数据
- 获得Plist文件的全路径
NSBundle *bundle = [NSBundle mainBundle];
NSString *path = [bundle pathForResource:@"imageData" ofType:@"plist"];
- 加载plist文件
_images = [NSArray arrayWithContentsOfFile:path];
- (NSArray *)images
{
if (_images == nil) {
NSBundle *bundle = [NSBundle mainBundle];
NSString *path = [bundle pathForResource:@"imageData" ofType:@"plist"];
_images = [NSArray arrayWithContentsOfFile:path];
}
return _images;
}
NSBundle
1> 一个NSBundle代表一个文件夹,利用NSBundle能访问对应的文件夹
2> 利用mainBundle就可以访问软件资源包中的任何资源
3> 模拟器应用程序的安装路径
汤姆猫
连续播放多张图片
#import "MJViewController.h"
@interface MJViewController ()
- (IBAction)drink;
- (IBAction)knock;
- (IBAction)rightFoot;
/** 这是一只显示图片的猫 */
@property (weak, nonatomic) IBOutlet UIImageView *tom;
@end
@implementation MJViewController
/** 播放动画 */
- (void)runAnimationWithCount:(int)count name:(NSString *)name
{
if (self.tom.isAnimating) return;
// 1.加载所有的动画图片
NSMutableArray *images = [NSMutableArray array];
for (int i = 0; i<count; i++) {
// 计算文件名
NSString *filename = [NSString stringWithFormat:@"%@_%02d.jpg", name, i];
// 加载图片
// imageNamed: 有缓存(传入文件名)
// UIImage *image = [UIImage imageNamed:filename];
// imageWithContentsOfFile: 没有缓存(传入文件的全路径)
NSBundle *bundle = [NSBundle mainBundle];
NSString *path = [bundle pathForResource:filename ofType:nil];
UIImage *image = [UIImage imageWithContentsOfFile:path];
// 添加图片到数组中
[images addObject:image];
}
self.tom.animationImages = images;
// 2.设置播放次数(1次)
self.tom.animationRepeatCount = 1;
// 3.设置播放时间
self.tom.animationDuration = images.count * 0.05;
[self.tom startAnimating];
// 4.动画放完1秒后清除内存
CGFloat delay = self.tom.animationDuration + 1.0;
[self.tom performSelector:@selector(setAnimationImages:) withObject:nil afterDelay:delay];
// [self performSelector:@selector(clearCache) withObject:nil afterDelay:delay];
}
//- (void)clearCache
//{
self.tom.animationImages = nil;
//
// [self.tom setAnimationImages:nil];
//}
- (IBAction)drink {
[self runAnimationWithCount:81 name:@"drink"];
// if (self.tom.isAnimating) return;
//
// // 1.加载所有的动画图片
// NSMutableArray *images = [NSMutableArray array];
//
// for (int i = 0; i<81; i++) {
// // 计算文件名
// NSString *filename = [NSString stringWithFormat:@"drink_%02d.jpg", i];
// // 加载图片
// UIImage *image = [UIImage imageNamed:filename];
// // 添加图片到数组中
// [images addObject:image];
// }
// self.tom.animationImages = images;
//
// // 2.设置播放次数(1次)
// self.tom.animationRepeatCount = 1;
//
// // 3.设置播放时间
// self.tom.animationDuration = images.count * 0.05;
//
// [self.tom startAnimating];
}
- (IBAction)knock {
[self runAnimationWithCount:81 name:@"knockout"];
}
- (IBAction)rightFoot {
[self runAnimationWithCount:30 name:@"footRight"];
}
@end
文档注释的写法:
/** 这是一只显示图片的猫 */
这样写可以在调用的时候显示注释
苹果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时,编译器不会有任何友善提示,需要手敲
dict[@"name"] = @"Jack";
NSString *name = dict[@"name"];
- 手敲字符串key,key容易写错
- Key如果写错了,编译器不会有任何警告和报错,造成设错数据或者取错数据
使用模型的好处
- 所谓模型,其实就是数据模型,专门用来存放数据的对象,用它来表示数据会更加专业
- 模型设置数据和取出数据都是通过它的属性,属性名如果写错了,编译器会马上报错,因此,保证了数据的正确性
- 使用模型访问属性时,编译器会提供一系列的提示,提高编码效率
app.name = @"Jack”;
NSString *name = app.name;
字典转模型
字典转模型的过程最好封装在模型内部
模型应该提供一个可以传入字典参数的构造方法
- (instancetype)initWithDict:(NSDictionary *)dict;
+ (instancetype)xxxWithDict:(NSDictionary *)dict;
- (void)setValue:(id)value forUndefinedKey:(nonnull NSString *)key
{
if ([key isEqualToString:@"id"]) {
self.ID = value;
}
}
- (instancetype)initWithDict:(NSDictionary *)dict
{
if (self = [super init]) {
[self setValuesForKeysWithDictionary:dict];
}
return self;
}
+ (instancetype)recipeWithDict:(NSDictionary *)dict
{
return [[self alloc] initWithDict:dict];
}
for (NSDictionary *dict in dataDict) {
RecipeModel *model = [RecipeModel recipeWithDict:dict];
// NSLog(@"%@", model);
[self.dataArray addObject:model];
}
instancetype
instancetype在类型表示上,跟id一样,可以表示任何对象类型
instancetype只能用在返回值类型上,不能像id一样用在参数类型上
instancetype比id多一个好处:编译器会检测instancetype的真实类型
Xib文件的使用
Xib文件可以用来描述某一块局部的UI界面
Xib文件的加载
- 方法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
UINib *nib = [UINib nibWithNibName:@"MJAppView" bundle:[NSBundle mainBundle]];
NSArray *objs = [nib instantiateWithOwner:nil options:nil];
- 在开发阶段,面向开发者的是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就是控制器)
self.scrollView.delegate = self;
- 通过storyboard拖线(右击UIScrollView)
UIScrollView的常见属性
@property(nonatomic) CGPoint contentOffset;
这个属性用来表示UIScrollView滚动的位置
@property(nonatomic) CGSize contentSize;
这个属性用来表示UIScrollView内容的尺寸,滚动范围(能滚多远)
@property(nonatomic) UIEdgeInsets contentInset;
这个属性能够在UIScrollView的4周增加额外的滚动区域
UIScrollView的代理(delegate)
- 很多时候,我们想在 UIScrollView 正在滚动 或 滚动到某个位置 或者 停止滚动 时做一些特定的操作
- 要想完成上述功能,前提条件就是能够监听到 UIScrollView 的整个滚动过程
- 当 UIScrollView 发生一系列的滚动操作时, 会自动通知它的代理(delegate)对象,给它的代理发送相应的消息,让代理得知它的滚动情况
- 也就是说,要想监听 UIScrollView 的滚动过程,就必须先给 UIScrollView 设置一个代理对象,然后通过代理得知 UIScrollView 的滚动过程
// 用户开始拖拽时调用
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;
// 滚动到某个位置时调用
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
// 用户结束拖拽时调用
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
内容缩放
UIScrollView不仅能滚动显示大量内容,还能对其内容进行缩放处理
也就是说,要完成缩放功能的话,只需要将需要缩放的内容添加到UIScrollView中
UIScrollView的缩放原理
当用户在UIScrollView身上使用捏合手势时,UIScrollView会给代理发送一条消息,询问代理究竟要缩放自己内部的哪一个子控件(哪一块内容)
// 用户使用捏合手势时调用
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView;
当用户在 UIScrollView 身上使用捏合手势时,UIScrollView 会调用代理的 viewForZoomingInScrollView:
方法,这个方法返回的控件就是需要进行缩放的控件
缩放实现步骤
- 设置UIScrollView的id delegate代理对象
- 设置minimumZoomScale :缩小的最小比例
- 设置maximumZoomScale :放大的最大比例
- 让代理对象实现下面的方法,返回需要缩放的视图控件
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView;
跟缩放相关的其他代理方法
- 缩放完毕的时候调用
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
- 正在缩放的时候调用
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
分页
只要将 UIScrollView的pageEnabled 属性设置为 YES ,UIScrollView 会被分割成多个独立页面,里面的内容就能进行分页展示
一般会配合 UIPageControl 增强分页效果,UIPageControl 常用属性如下
- 一共有多少页
@property(nonatomic) NSInteger numberOfPages;
- 当前显示的页码
@property(nonatomic) NSInteger currentPage;
- 只有一页时,是否需要隐藏页码指示器
@property(nonatomic) BOOL hidesForSinglePage;
- 其他页码指示器的颜色
@property(nonatomic,retain) UIColor *pageIndicatorTintColor;
- 当前页码指示器的颜色
@property(nonatomic,retain) UIColor *currentPageIndicatorTintColor;
NSTimer
NSTimer叫做“定时器”,它的作用如下
- 在指定的时间执行指定的任务
- 每隔一段时间执行指定的任务
调用下面的方法就会开启一个定时任务
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti
target:(id)target
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats
每隔ti秒,调用一次aTarget的aSelector方法,yesOrNo决定了是否重复执行这个任务
通过invalidate方法可以停止定时器的工作,一旦定时器被停止了,就不能再次执行任务。只能再创建一个新的定时器才能执行新的任务
- (void)invalidate;
UITableView
在众多移动应用中,能看到各式各样的表格数据
在iOS中,要实现表格数据展示,最常用的做法就是使用UITableView
UITableView继承自UIScrollView,因此支持垂直滚动,而且性能极佳
如何展示数据
UITableView需要一个数据源(dataSource)来显示数据
UITableView会向数据源查询一共有多少行数据以及每一行显示什么数据等
没有设置数据源的UITableView只是个空壳
凡是遵守UITableViewDataSource协议的OC对象,都可以是UITableView的数据源
UITableViewDataSource协议
// 一共有多少组数据
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
// 每一组有多少行数据
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
// 每一行显示什么内容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
@property (nonatomic, assign) id <UITableViewDataSource> dataSource;
tableView展示数据的过程
调用数据源的下面方法得知一共有多少组数据
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
调用数据源的下面方法得知每一组有多少行数据
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
调用数据源的下面方法得知每一行显示什么内容
- (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的重用代码
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1.定义一个cell的标识
static NSString *ID = @"mjcell";
// 2.从缓存池中取出cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 3.如果缓存池中没有cell
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}
// 4.设置cell的属性...
return cell;
}
使用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)的代理方法,通知代理发生了什么事情
(在调用之前判断代理是否实现了该代理方法)