文章目录
- UIKit
- 常用的UIKit组件
- 懒加载的优势
- CALayer和UIView
- 区别
- 关系
- UITableView
- UITableView遵循的两个delegate以及必须实现的方法
- 上述四个必须实现方法执行顺序
- 其他方法的执行顺序:
- UICollectionView和UITableView的区别
- UICollectionViewFlowLayout和UICollectionViewLayout区别
- UICollectionViewFlowLayout自定义时要重写的方法
- 手动计算行高的两种方式
- boundingRectWithSize
- ViewController生命周期
- UIViewController和UIResponder
- UIControl
- UIResponder
- frame和bounds
- 何时使用Frame,何时使用Bounds
UIKit
#import <UIKit/UIKit.h>
UIKit 框架提供了 iOS 或 Apple tvOS App 所需的基础架构。它提供了用于实施界面的窗口和视图架构,用于向 App 提供多点触控和其他类型输入的事件处理基础架构,以及管理用户、系统和 App 之间互动所需的主运行循环。该框架提供的其他功能包括动画支持、文档支持、绘图和打印支持、当前设备的相关信息、文本管理和显示、搜索支持、辅助功能支持、App 扩展支持和资源管理。
常用的UIKit组件
- UIView及子类,包括UIScrollView(UITableView、UICollectionView、UITextView)、UILabel、UIControl(UIButton、UITextField)、UIPickerView等;
- 与控件相关但不能被人所直观看到的图形、绘图、打印、文本等的配置与控制,包括UIViewController、UIImage等。
懒加载的优势
懒加载(延迟加载),把对象的实例化尽量延迟,即启动应用程序时不加载这个资源,只有在运行时用到才加载(按需加载)。
例如,如果启动APP后一次性加载大量数据、图片和音视频等资源,就有可能会耗尽移动设备内存。这时就可以使用懒加载技术。具体来说,重写属性的getter方法,先判断该属性是否为nil,如果为空再进行实例化,否则直接返回属性。
懒加载的好处有:不必将创建对象的代码全部写在viewDidLoad方法中,代码的可读性更强;每个控件分别负责各自的实例化处理,代码的耦合程度低;不必在初始化阶段加载所有数据,节省内存,也就是系统的内存占用率会减少;减少服务器端压力。
CALayer和UIView
区别
UIView
继承自UIResponder
,主要负责事件传递、事件响应,属于基于UIKit
框架
CALayer
继承自NSObject
,负责图像渲染,动画和视图的显示,属于QuartzCore
框架
这两大内容符合单一指责原则
虽然CALayer
没有事件响应的能力,但是我们可以通过
hitTest
convert
两个方法来判断事件是不是在layer
上,从而来给事件添加点击事件
关系
- 所有的界面元素都继承自
UIView
。它真正的绘图部分是由CALayer
的类来管理的。UIView
本身更像是一个CALayer
的管理器,访问其跟绘图和坐标有关的属性,例如frame
和bounds
等等,实质上内部都是在访问它所包含的CALayer
的相关属性。 UIView
有个layer
属性,可以返回它的主CALayer
实例。UIView
有一个layerClass
方法,返回主layer
所使用的类(默认返回就是[CALayer class]
),UIView
的子类可以通过重载这个方法来时UIView
使用不同的CALayer
来显示。UIView
的CALayer
类似于UIView
的子view
树状结构,也可以向它的layer
上添加子layer
,来完成某些特殊的表示
UIView *firstView = [[UIView alloc] init];firstView.frame = CGRectMake(200, 200, 200, 200);firstView.backgroundColor = [UIColor redColor];[self.view addSubview:firstView];CALayer *layer = [[CALayer alloc] init];layer.backgroundColor = [[UIColor greenColor] CGColor];layer.position = CGPointMake(100,100); //中心点layer.bounds = CGRectMake(100,100,80,80);[firstView.layer addSublayer:layer];
并没有添加新图层,而是在原本view上添加的新图层,这和addsubview并不一样:
CALayer
视图结构类似UIView
的子view
树形结构,可以向它的layer
上添加子layer
,类似于向View
上添加View
,来完成某些特殊的表示。UIVIew
的layer
树形在系统内部,被系统维护三份copy
-
- 第一份,
逻辑树
,代码可以在里面操作,例如通过代码更改layer的属性【比如frame\bounds】
就在这一份进行操作
- 第一份,
-
- 第二份,
动画树
,这是一个中间层,系统在这一层更改属性,进行各种渲染操作
- 第二份,
-
- 第三份,
显示树
,这棵树的内容就是当前正被显示在屏幕上的内容
- 第三份,
UITableView
UITableView遵循的两个delegate以及必须实现的方法
UITableView
需要一个数据源(dataSource
)来显示数据,UITableView
会向数据源查询一共有多少行数据以及每一行显示什么数据等等。没有设置数据源的UITableView
只是个空壳。凡是遵守UITableViewDataSource
协议的OC对象,都可以是UITableView
的数据源。- 我们也需要为
UITableView
设置代理对象(delegate
),以便在UITableView
触发某些事件时做出相应的处理,比如选中了某一行。凡是遵守了UITableViewDelegate
协议的OC对象。都可以是UITableView
的代理对象,一般会让控制器充当UITableView
的dataSource
和delegate
,通过我们手动实现协议中的某些方法来完成tableView
的实现
dataSource:
//返回组数
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView//返回每组里的行数
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section //cell的实现
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
delegate:
//返回每行高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath//不必须实现,cell点击事件
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
上述四个必须实现方法执行顺序
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {NSLog(@"numberOfSectionsInTableView");return 1;
}- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {NSLog(@"numberOfRowsInSection");return 1;
}- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)
indexPath {UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];NSLog(@"cellForRowAtIndexPath");return cell;
}- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {NSLog(@"heightForRowAtIndexPath");return 20;
}
结果:
- numberOfSectionsInTableView
- numberOfRowsInSection
- cellForRowAtIndexPath
- heightForRowAtIndexPath
其他方法的执行顺序:
第一轮:
1、numberOfSectionsInTableView :假如section=2,此函数只执行一次,假如section=0,函数不执行,默认为1
2、heightForHeaderInSection ,执行两次,此函数执行次数为section数目
3、heightForFooterInSection ,函数属性同上,执行两次
4、numberOfRowsInSection ,此方法执行一次
5、heightForHeaderInSection ,此方法执行了两次,我其实有点困惑为什么这里还要调用这个方法
6、heightForFooterInSection ,此方法执行两次,
7、numberOfRowsInSection,执行一次
8、heightForRowAtIndexPath ,行高,先执行section=0,对应的row次数
第二轮:
1、numberOfSectionsInTableView ,一次
2、heightForHeaderInSection ,section次数
3、heightForFooterInSection ,section次数
4、numberOfRowsInSection ,一次
5、heightForHeaderInSection ,执行section次数
6、heightForFooterInSection,执行section次数
7、numberOfRowsInSection,执行一次
8、heightForRowAtIndexPath,行高,先执行一次
9、cellForRowAtIndexPath
10、willDisplayCell
然后8、9、10依次执行直到所有的cell被描画完毕
UICollectionView和UITableView的区别
- 每行可以展示多个,更多样的布局方式
- 由于一行可以展示多个视图,row不能准确的表达,所以引入了item
- 所有视图都只能由我们自定义实现(我们必须实现自定义cell)
就自定义样式而言,Layout中有一个属性为UICollectionViewLayoutAttributes
@property (nonatomic) CGRect frame;
@property (nonatomic) CGPoint center;
@property (nonatomic) CGSize size;
@property (nonatomic) CATransform3D transform3D;
@property (nonatomic) CGRect bounds API_AVAILABLE(ios(7.0));
@property (nonatomic) CGAffineTransform transform API_AVAILABLE(ios(7.0));
@property (nonatomic) CGFloat alpha;
@property (nonatomic) NSInteger zIndex; // default is 0
@property (nonatomic, getter=isHidden) BOOL hidden; // As an optimization, UICollectionView might not create a view for items whose hidden attribute is YES
@property (nonatomic, strong) NSIndexPath *indexPath;@property (nonatomic, readonly) UICollectionElementCategory representedElementCategory;
@property (nonatomic, readonly, nullable) NSString *representedElementKind; // nil when
可以看到UICollectionViewLayoutAttributes
的实例中包含了需要诸如边框、中心点、大小、形状、透明度、层级关系、是否隐藏等等信息。
每一个cell都会对应一个Attributes
。整体的逻辑,通过设置默认的layout
,layout
中设置每一个cell对应的Attributes
,然后将整个的layout
赋值给collectionView
。
UICollectionViewFlowLayout和UICollectionViewLayout区别
UICollectionViewLayout
是一个抽象类 一般的,抽象类只定义了一些子类公有的属性和行为,不能直接使用。UICollectionViewFlowLayout
是流水布局,UI控件会像流水一样,一行排满了自动下一行排。
UICollectionViewFlowLayout自定义时要重写的方法
-(void)prepareLayout
prepare方法被自动调用,以保证layout实例的正确。-(CGSize)collectionViewContentSize
返回collectionView的内容的尺寸-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect1. 返回rect中的所有的元素的布局属性2. 返回的是包含UICollectionViewLayoutAttributes的NSArray3. UICollectionViewLayoutAttributes可以是cell,追加视图或装饰视图的信息,通过不同的UICollectionViewLayoutAttributes初始化方法可以得到不同类型的UICollectionViewLayoutAttributes:1)layoutAttributesForCellWithIndexPath:2)layoutAttributesForSupplementaryViewOfKind:withIndexPath:3)layoutAttributesForDecorationViewOfKind:withIndexPath:-(UICollectionViewLayoutAttributes )layoutAttributesForItemAtIndexPath:(NSIndexPath )indexPath
返回对应于indexPath的位置的cell的布局属性-(UICollectionViewLayoutAttributes )layoutAttributesForSupplementaryViewOfKind:(NSString )kind atIndexPath:(NSIndexPath *)indexPath
返回对应于indexPath的位置的追加视图的布局属性,如果没有追加视图可不重载-(UICollectionViewLayoutAttributes * )layoutAttributesForDecorationViewOfKind:(NSString)decorationViewKind atIndexPath:(NSIndexPath )indexPath
返回对应于indexPath的位置的装饰视图的布局属性,如果没有装饰视图可不重载-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
当边界发生改变时,是否应该刷新布局。如果YES则在边界变化(一般是scroll到其他地方)时,将重新计算需要的布局信息。
调用顺序:
1)-(void)prepareLayout a. collection view 只会在第一次layout的时候调用一次`-prepareLayout`,作为第一次通知layout实例对象的消息b. collection view 会在 layout 对象 invalidated 之后并且requerying之前再次调用c. 继承自UICollectionViewLayout都需要重写这个方法,一般都是在这个方法里面准备好`layoutAttributesForElements(in:)`这个方法要使用到的`UICollectionViewLayoutAttributes`数组。2) -(CGSize) collectionViewContentSize a. 确定collectionView的所有内容的尺寸b. 每一次移动collection view时都会调用这个方法,并且这个方法会被调用多次3)-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect初始的layout的外观将由该方法返回的UICollectionViewLayoutAttributes来决定。4)在需要更新layout时,需要给当前layout发送 1)-invalidateLayout, 该消息会立即返回,并且预约在下一个loop的时候刷新当前layout2)-prepareLayout,3)依次再调用-collectionViewContentSize和-layoutAttributesForElementsInRect来生成更新后的布局。
手动计算行高的两种方式
- sizeToFit
- boundingRectWithSize
sizeToFit和sizeThatFit区别:
-(CGSize)sizeThatFits:(CGSize)size;
会返回一个最适合的 Size,但是不会改变原来视图的frame 的size-(void)sizeToFit;
内部会调用 - (CGSize)sizeThatFits:(CGSize)size; 方法获取一个最适合的size, 并使用这个size 来调整当前视图的frame 的size.
简单的说:- (CGSize)sizeThatFits:(CGSize)size;
和 - (void)sizeToFit;
都或获取一个最适合的Size, 但是- (CGSize)sizeThatFits:(CGSize)size;
不会改变原始图的frame,仅仅是返回一个size, 而- (void)sizeToFit;
会为你计算一个最合适的Size的同时,并根据这个Size来调整你当前视图的frame
boundingRectWithSize
返回文本绘制所占据的矩形空间。
CGRect rect=[(NSString *)obj boundingRectWithSize:CGSizeMake(1000, FONTHEIGHT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16]} context:nil].size.width;
里面的参数如下:
obj
是指要计算显示的字符串boundingRectWithSize
表示计算的宽高限制- 计算高度时,需要宽度固定:
CGSizeMake(1000, CGFLOAT_MAX)
- 这里的1000也可以用已经确定的控件的宽度替代
self.label.width
- 计算结果表示在宽度最多为1000高度不限时,显示完全字符串需要的高度
- 计算宽度时,需要高度固定:
CGSizeMake(CGFLOAT_MAX, 200)
- 同理200也可以用已知高度替换:
self.label.height
- 计算结果表示在高度不超过200时,将给定的字符串现实完全需要的宽度
options
是文本绘制的附加选项,NSStringDrawingUsesLineFragmentOrigin
是默认基线attributes
字典格式,限定字符串显示的样式,一般限制字体较多,比如:@{NSFontAttributeName:[UIFont systemFontOfSize:16]}
context
包括一些信息,例如如何调整字间距以及缩放。最终,该对象包含的信息将用于文本绘制。一般写nil。
ViewController生命周期
【iOS】—— ViewController生命周期
loadView
:加载view。这个方法中,要正式加载View了,控制器 view 是通过懒加载的方式进行加载的,即用到的时候再加载。在 view 加载过程中首先会调用 loadView 方法,在这个方法中主要完成一些关键 view 的初始化工作。viewWillAppear
: 视图将要显示viewWillLayoutSubviews
:控制器的view将要布局子控件viewDidLayoutSubviews
: 控制器的view布局子控件完成viewDidAppear
: 视图已经显示viewWillDisappear
: 视图将要消失viewDidDisappear
: 视图已经消失
UIViewController和UIResponder
我们最熟悉的UIApplication
、UIView
、UIViewController
这几个类是直接继承自UIResponder
,UIResponder
类是专门用来响应用户的操作处理各种事件(UIEvent
)的。
UIResponder
提供了用户点击、按压检测(presses
)以及手势检测(motion
)的回调方法,分别对应用户开始、移动、结束以及取消,其中只有在程序强制退出或者来电时,取消事件才会调用。
UIControl
UIControl
建立在视图上,增加了更多的交互支持。最重要的是,它增加了 target / action
模式。看一下具体的子类,我们可以看一下按钮,日期选择器 (Date pickers
),文本框等等。创建交互控件时,你通常想要子类化一个 UIControl
。一些常见的像 bar buttons
(虽然也支持 target / action
) 和 textView
(这里需要你使用代理来获得通知) 的类其实并不是 UIControl
。
UIResponder
UIResponder
是 UIView
的父类。responder
能够处理触摸、手势、远程控制等事件。之所以它是一个单独的类而没有合并到 UIView
中,是因为 UIResponder
有更多的子类,最明显的就是 UIApplication
和 UIViewController
。通过重写 UIResponder
的方法,可以决定一个类是否可以成为第一响应者 (first responder
),例如当前输入焦点元素。
当 touches
(触摸) 或 motion
(指一系列运动传感器) 等交互行为发生时,它们被发送给第一响应者 (通常是一个视图)。如果第一响应者没有处理,则该行为沿着响应链到达视图控制器,如果行为仍然没有被处理,则继续传递给应用。如果想监测晃动手势,可以根据需要在这3层中的任意位置处理。
UIResponder
还允许自定义输入方法,从 inputAccessoryView
向键盘添加辅助视图到使用 inputView
提供一个完全自定义的键盘。
frame和bounds
Frame
: 视图的位置和大小使用是父视图的坐标系,所以将视图放置在父级中这一点就很重要。Bounds
:视图的位置和大小,使用的是其自己的坐标系,而对于这一点而言将视图的内容或子视图放置在其自身内很重要。
我们先来初始化一个View看看:
UIView *firstView = [[UIView alloc] init];firstView.frame = CGRectMake(200, 200, 200, 200);firstView.backgroundColor = [UIColor redColor];[self.view addSubview:firstView];NSLog(@"frame = %@\n", NSStringFromCGRect(firstView.frame));NSLog(@"bounds = %@", NSStringFromCGRect(firstView.bounds));
然后我们给这个View旋转90度看看:
firstView.transform = CGAffineTransformMakeRotation(M_PI * 0.25);
可以看到Bounds
仍然相同,但是Frame
已经发生了更改。现在更容易看出frame
和bounds
之间的区别。
何时使用Frame,何时使用Bounds
- 由于
frame
关联视图在其父视图中的位置,因此您在进行向外更改时会使用它,例如更改其宽度或查找视图与其父视图顶部之间的距离。 - 使用
bounds
时,你正在向内变化,就像画的东西或视图中安排子视图。如果您对它进行了一些转换,还可以使用bounds
来获取视图的大小。