iOS平台和UI综述

315 阅读9分钟

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

网格状