1 Application和UIViewController
1.1 Application
应用启动后,系统创建的第一个对象就是UIApplication对象,一个应用程序对应一个UIApplication,其作为单例存在,通过AppDelegate可以实现一些和UIApplication的交互
获取到该对象可以进行应用级别的操作,用户不能自己创建UIApplication对象
UIApplication *app = [UIApplication sharedApplication];
应用级别的操作包括
- 程序右上角的提醒数字
- 修改状态栏
- openUrl(电话、网页、邮件、打开其他APP)
1.1.1 应用程序入口
应用程序从mian.m进入
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
/**
argc:argv当中的参数个数,通常和main传入的对应
argv:参数列表,通常和main当中的传入的对应
nil: UIApplication或其子类的类名,nil表示@"UIApplication"
appdeleagteClassName: UIApplication的代理的类名
**/
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
通过UIApplicationMain(...)主要完成了以下工作:
- 创建了一个application对象
- 设置了代理
- 创建了一个事件循环(Runloop)
- 读取了info.plist文件
- 创建了一个window
- 加载了第一个控制器(ViewController)
1.1.2 应用程序生命周期
应用程序存在如下的一些状态,按照优先级,如果内存不足将会从底优先级开始清理应用程序
- Active: 应用就处于当前屏幕
- Inactive: 应用程序位于前台但是不能交互,比如存在电话接入、home键产看当前所有运行程序、通知中心下拉
- Background: 通过home键回到手机主界面,程序经过特殊处理后在后台也可以运行
- Suspended: 应用长时间退到后台会被挂起,此时应用程序的内存依然为其保留,但是失去了代码执行权限
- NotRunning
typedef NS_ENUM(NSInteger, UIApplicationState) {
UIApplicationStateActive, // 应用处于活跃状态
UIApplicationStateInactive, // 应用处于非活跃状态
UIApplicationStateBackground // 应用处于后台
} NS_ENUM_AVAILABLE_IOS(4_0);
AppDelegate当中的执行次序
//程序完成加载
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(nullable NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions API_AVAILABLE(ios(3.0));
//程序被激活
- (void)applicationDidBecomeActive:(UIApplication *)application;
//程序取消激活
- (void)applicationWillResignActive:(UIApplication *)application;
//程序进入后台
- (void)applicationDidEnterBackground:(UIApplication *)application API_AVAILABLE(ios(4.0));
//从后台回到前台
- (void)applicationWillEnterForeground:(UIApplication *)application API_AVAILABLE(ios(4.0));
//再次被激活
- (void)applicationDidBecomeActive:(UIApplication *)application;
...
//内存不够
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application;
//程序即将关闭
- (void)applicationWillTerminate:(UIApplication *)application;
1.1.3 UI配置
设置窗口和根视图控制器
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.backgroundColor = [UIColor whiteColor];
//...
[self.window makeKeyAndVisible];
return YES;
}
1.2 ViewController
- 管理视图层级
- 一般来说一个ViewController管理一个页面
- 每个ViewController都有个View作为其管理的视图的根视图
- 处理视图相关事件
1.2.1 ViewController的生命周期
initWithCoder 通过nib文件(storyboard或者xib)初始化时触发
awakeFromNib: nib文件被加载时发送消息1到nib文件的每个对象
loadView: 加载视图控制器自带的视图
viewDidLoad:视图控制器的视图加载完成
viewWillAppear:视图将要显示在window上
updateViewConstraints: 视图开始更新AutoLayout约束
viewWillLayoutSubviews:视图将要更新子视图的位置
viewDidLayoutSubviews:视图的子视图位置更新完成
viewDidAppear: 视图已经展示到window上
viewWillDisappear: 视图将要从window上消失
viewDidDisappear: 视图已经从window上消失
当从VC1 push 到VC2的时候,当VC2的视图didLoad的时候,VC1的视图开始WillDisappear;当VC1的视图完成子视图的布局时,VC2 DisDisappear
1.2.2 viewController的跳转方式
- push pop
- present dismiss
- 利用StoryBoard的sogue
1.2.3 常见的ViewController
- UIViewController
- UINavigationController
- UITabBarController
2 UI布局
2.1 坐标系和视图层级
2.1.1 UIWindow
- 用户界面的画布,App可能有1个以上的window
- 一般情况下是视图层级中的根视图,UIWindow继承于UIView
- 负责分发事件给子视图
2.1.2 View的生命周期
2.1.3 frame、bounds和center
- frame的坐标系相对于superview,通常起点为右上角
- bounds的坐标系相对于自身,通常起点是(0,0)
- center是其anchorPoint相对于superview的位置,anchorPoint一般是视图的中心
2.2 布局方式
2.2.1 手动布局(frame-based layout)
通常用frame描述view在其superview中的位置(origin)和大小(size),因此布局往往是在给view的frame设置合适的位置和大小
- 优点:简单、高性能
- 缺点:可维护性差、手机设备的尺寸变化
2.2.2 自动布局(autolayout)
- iOS设备碎片化,iOS6引入autolayout
- 类似Android的RelativeLayout
- 通过constraints计算orgin和size,constraints不可冲突,一般4条就足够了
- 系统提供的API过于冗长,业界探索简练的语法DSL Masonry/SnapKit
3 事件响应
3.1 UIEvent
- 把屏幕上检测到的点事件用UITouch对象来表示,最终被封装成UIEvent作为事件的消息载体在响应链上传递
- 发生触摸事件后,系统会将该事件加入到由UIApplication管理的事件队列
- UIApplication会从事件队列中取出最前面的事件,将事件分发下去以便处理,通常先发送事件给应用程序的主窗口
- 主窗口会在视图的层次结构中找到一个最适合的视图处理触摸事件
3.2 响应链
iOS中的响应者链(Responder Chain)是用于确定事件响应者的一种机制,其中事件主要指触摸事件(Touch Event),该机制和UIKit中的UIResponder类紧密相关。响应触摸事件的都是屏幕上的界面元素,必须继承自UIResponder类的界面类(包括各种常见的视图类及其视图控制器类,如UIView和UIViewController)才可以响应触摸事件
- hitTest方法命中视图 hitTest方法从顶部Application往下调用,直到找到命中者
- 响应者链确定响应者 从命中者视图沿着响应者链往上传递寻找真正的响应者
3.2.1 hitTest
hitTest方法递归寻找命中者, 检测当前视图是否被命中
pointInside方法检测当前视图是否被命中,即触摸点坐标是否在视图内部
触摸事件发生后,系统将触摸事件以UIEvent的方式加入到UIApplication的事件队列中,UIApplication将事件分发给根部的UIWindow去处理,UIWindow开始调用hitTest方法进行迭代命中检测
命中检测迭代过程:如果触摸点在当前视图内,那么递归对当前视图内部所有的子视图进行命中检测;如果不再当前视图内,返回NO停止迭代,这样会最终会确定屏幕上最顶部的命中的视图元素,即命中者
当前的界面以window为根节点形成树形层级结构,寻找命中者是一个深度优先搜索的过程。
3.2.2 响应者链
找到命中者后,任务并没有完成,因为最终的命中者不一定是事件的响应者。
响应是指对于视图针对某个事件绑定某个方法,在事件发生后执行该方法。
一个继承于UIResponder的视图想要能响应事件,还需要满足如下的一些条件:
- 必须要有对应的视图控制器,按照MVC模式响应函数的逻辑代码要写在控制器内
- userInteractionEnabled属性需要设置为YES
- hidden属性需要设置为NO,隐藏的视图不能响应事件,同理alpha过低(0.01)也会影响响应
- 保证树形结构的正确性,子节点的frame要在父节点的frame内部
响应者链对应视图节点树形结构的一条路径,命中者的下一个响应者是它的视图控制器(如果存在的话),如果不满足响应条件,则顺着响应链往上寻找,看父节点能否响应,不能则看父节点的视图控制器(如果存在的话),如果一直往上直到UIWindow事件依然没有被响应,事件会交给UIApplication结束响应循环,这种情况没有实际的响应动作发生。
3.3 寻找发生event的View
3.4 手势
3.4.1 手势类型
UITapGestureRecognizer(敲击)
UIPinchGestureRecognizer(捏合,用于缩放)
UIPanGestureRecognizer(拖拽)
UISwipeGestureRecognizer(轻扫)
UIRotationGestureRecognizer(旋转)
UILongPressGestureRecognizer(长按)
3.4.2 添加手势
3.4.3 UIGestureRecognizerDelegate
协调不同手势,处理冲突
4 常见控件
4.1 控件的一些通用特性
- UIView:frame、layer、transform、superview、subviews
- UIControl: enable、selected、highlighted、target/action/event
- Others: backgroundColor、text、backgroundImgae、font
UIControl/UIButton:继承UIView,封装了事件处理逻辑,提供便捷的API,供用户响应事件
4.2 控件分类
4.2.1 UILabel
显示固定的文本,外观可配置,可以显示普通的字符串或属性字符串,允许自定义标签中子字符串的外观
4.2.2 UIButton
UIButton继承于UIControl:
- 内置组合了label和imageview
- 不同state下,title/icon配置
- 封装了便捷的API接口,处理用户交互事件
4.2.3 UIImageView
- 用于显示PNG、JPEG等多种格式的图片,也可以显示图片序列的动图
- 不支持GIF的动图
- 图片支持三种不同的缩放策略,和多种显示模式
UIViewContentModeScaleToFill : 图片拉伸至填充整个UIImageView(变形拉伸)
UIViewContentModeScaleAspectFit : 图片拉伸至完全显示在UIImageView里面为止(等比拉伸完全显示)
UIViewContentModeScaleAspectFill : 等比拉伸,直到图片完全占据,溢出
UIViewContentModeRedraw : 调用了setNeedsDisplay方法时,就会将图片重新渲染
UIViewContentModeCenter : 居中显示
UIViewContentModeTop
UIViewContentModeBottom
UIViewContentModeLeft
UIViewContentModeRight
UIViewContentModeTopLeft
UIViewContentModeTopRight
UIViewContentModeBottomLeft
UIViewContentModeBottomRight
4.2.4 UIScrollView
- 用于显示内容超过屏幕界面的控件
- 可以垂直或水平滚动
- 可以支持手势的缩放操作
- 是很多可以滚动的控件的父类,包括UITableView和UITextView
- contentSize 内容实际大小,contentOffset 当前显示区域的偏移量
4.2.5 UITableView
字母表地名列表、QQ好友列表
- 基于UIScrollView来实现的,通过行和组的划分,来组织页面
- 使用UITableViewCell用于表示列表中的行
- 基于UITableViewCell实现了行级别视图的复用
- 优点:
- 高性能,cell复用回收,CPU内存友好
- 符合大多数App的结构化数据表达
- DataSource & Delegate
- 创建TableView
- 指定DataSource和Delegate协议
- 实现DataSource和Delegate协议
- 指定组(sections)的数量和各个组分别有多少行(rows)
- 指定N组M行cell高度
- 指定N组M行cell类型
- 每种类型的cell都对应了不同的cellID,当需要复用的时候查看池内是否存在对应类型的cell,如果有,取出来,重新渲染数据后显示,如果没有就新建
4.2.6 UICollectionView
网格状