UIKit入门

2,934 阅读7分钟

UIKit控件

现在要开启我们的“UI工程师”之旅了。

写UI的第一步,就是要先知道苹果爸爸给我们提供了怎样的控件,毕竟每个控件都要自己完成的话也太耗费精力啦。

首先来一个经典的iOS控件继承图,接下来可以参照这个图进行讲解:

(因为我原本是学Android的,所以在讲解iOS的过程中可能会增加一些Android、Java的对比讲解)

一、UIResponder

(苹果的文档:UIResponder)

UIResponder是UIApplication、UIView、UIViewController的基类,它是响应和处理事件的一个抽象接口。

UIResponder构成UIKit应用程序的事件处理主干。UIApplicaiton也是Responder,事件发生的 时候,UIKit会将事件派分到应用程序的根Responder,根据事件响应链进行传播和响应。(责任链模式) 这些事件包括:触摸事件、移动事件、远程控制事件和按下事件。

事件分发详细讲解(//TODO:链接)

二、UIViewController

(苹果的文档:UIViewController)

1.职责

UIViewController应该对应安卓里面的Acitvity,但有些方面又不完全一致。

ViewController的主要工作包括:

  • 更新view、管理view、修改view数据
  • 响应用户的交互
  • 管理view布局、调整view大小

2.对比UIViewController和Activity

在这里我基于我目前的理解,做一个简单的对比(往后还有新的理解体会,会继续补充):

UIViewController:(iOS)

  • 每个UIViewController都有对应的一个View
  • 每个应用程序都有一个rootViewController,可以为它添加子ViewController,可以调用方法来弹出新的ViewController
  • UIViewController可以嵌套,层级可以由程序员控制
  • deepLink(深度链接,其他应用可以直接链接到某一应用的指定页面)的话,还需要自己处理跳转(等我先学学)

Activity:(安卓)

  • 每个Acitivity都有一个RootView(ViewGroup)
  • 每个App可以有多个任务栈和多个Activity,Acitivity之间的关系相对平等,不可嵌套
  • Activity是四大组件之一,一系列的Activity由任务栈管理
  • Activity可以作为一个共享的界面组件给其他应用程序使用
  • Android也有对应的deepLink机制

3.常接触到的UIViewController

  • UITableViewController
  • UINavigationController
    • UIImagePickerController
    • UIVideoEditorController
  • UITabBarController
  • UICollectionViewController

三、UIView

(苹果的文档:UIView)

UIView在安卓里面就是对应View,但是安卓还区分了View和ViewGroup,只有ViewGroup能够添加子View,而iOS并不区分这两者。

1.View的渲染

UIView在界面中表示一个矩形的图形控件,作为事件响应的一个单元存在;管理控件图形的显示和,但不做具体的显示和渲染工作,显示内容的工作交给CALayout来做。

对于这点我觉得安卓的图形渲染和显示也与这个有类似,安卓是这样的:

  • 安卓中,View和ViewGroup负责参与事件响应链,还负责对自己及子View的进行mesure大小测量、layout布局、以及draw发起绘制(并不实际参与绘制)。
  • View不负责显示,而负责做显示工作的是surface(绘图表面)。但是View和surface并不是一一对应的,正常情况下,一个Activity只有一个surface,里面的所有View都在同一个surface上进行绘制(但是每个View都会有自己的canvas画布,作为绘制的入口),只有如SurfaceView这类视图控件,才会有自己独立的surface。
  • SurfaceFlinger是安卓中的一个负责显示的服务,它与每个要显示的surface是服务端与客户端的关系,每一个surface在SurfaceFlinger中作为一个layer存在。
  • SurfaceFlinger和需要显示服务的客户端之间通过一块匿名共享内存进行数据交换,这块匿名共享内存中被封装成一个个栈帧SharedBufferStack,每个栈帧表示一块绘图缓冲区,图像就在缓冲区中绘制。绘制完成后,前台显示的图像与在后台事先绘制好的图像交换,这样就完成了一帧的显示。这个机制叫双缓冲机制,能够防止在绘制过程中因为图像覆盖的原因导致的图像闪烁问题,不过实际上安卓并不止“双”缓冲,使用surfaceView来测试时,可以看到实际上是多个缓冲区图像在轮换。

以上提到的双缓冲机制十分常见,特别是在游戏这种对帧率有要求的程序中常有应用,iOS中的渲染也有用到。一个十分经典的示意图如下:

渲染的流程是这样的,硬件时钟会发出VSync(垂直同步信号),然后App的代码会使用CPU会去计算屏幕要显示的内容,之后将计算好的内容提交到GPU去渲染。随后,GPU将渲染结果提交到帧缓冲区,等到下一个VSync到来时将缓冲区的帧显示到屏幕上。

通常的屏幕显示是一秒60帧,如果CPU或者GPU没能在16.7ms时间内产生一帧画面,则会导致掉帧和界面卡顿。

掉帧的解决方式:

iOS:

  • iOS的优化分为CPU和GPU两部分,CPU方面,将以下操作放到子线程中:
    • 对象创建、调整、销毁
    • 预排版(布局计算、文本计算、缓存高度等等)
    • 预渲染(文本等异步绘制,图片解码等)
  • GPU方面可以优化的有:
    • 减少纹理渲染,视图混合的工作量
    • 尽量避免GPU离屏渲染。通常GPU在做渲染的时候是很快的,但是涉及到离屏渲染的时候情况就可能有些不同,因为需要额外开辟一个新的缓冲区进行渲染,然后绘制到当前屏幕的过程需要做onscreen跟offscreen上下文之间的切换,这个过程的消耗会比较昂贵。GPU离屏渲染何时会触发呢?
      • 为图层设置遮罩(layer.mask)
      • 将图层的layer.masksToBounds / view.clipsToBounds属性设置为true
      • 将图层layer.allowsGroupOpacity属性设置为YES和layer.opacity小于1.0
      • 为图层设置阴影(layer.shadow *)。
      • 为图层设置layer.shouldRasterize=true
      • 具有layer.cornerRadius,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing的图层文本(任何种类,包括UILabel,CATextLayer,Core Text等)。
      • 使用CGContext在drawRect :方法中绘制大部分情况下会导致离屏渲染,甚至仅仅是一个空的实现。

关于iOS为什么在进行这些操作时会离屏渲染,有篇文章讲得还不错:关于iOS离屏渲染的深入研究github上也有一篇讲性能优化的

Android:

  • App的绘制工作一般都在主线程中进行,不要在主线程中做太多工作,这样会导致主线程无法按时接收到绘制的消息。
  • 简化界面布局的复杂程度,使用来进行布局优化。
  • 防止过度绘制。过度绘制是指画面中同一个像素,因为图像覆盖的原因被绘制多次。将控件的背景设置为透明可以让过度绘制的区域减少。可以在开发者选项中打开Show GPU Overdraw选项,查看过度绘制情况。
  • 如果仍然不能满足性能需求,可以使用surfaceView等控件,它们允许在后台线程进行绘制。

2.各个VIew的详细介绍(慢慢补齐链接吧)

  • UINavigationBar
  • UIPickerView
  • UIImageView
  • UIControl
    • UIButton
    • UITextField
    • UISegmentedControl
    • UISwitch
    • UISlider
    • UIDataPicker
    • UIPageControl
  • UISrcollView
    • UITableView
    • UICollectionView
    • UITextView
  • UITableViewCell
  • UIActivityIndicatorView
  • UIProgressView
  • UILabel
  • UIWebView
  • UISearchBar
  • UIAlertView
  • UIWindow
  • UIActionSheet
  • UITableBar
  • UIToolBar

四、UIApplication

(苹果的文档:UIApplication)

在安卓中也有Application类,我认为职责应该差不多。

  1. UIApplication对象是应用程序的象征。
  2. 每一个应用程序都有自己的UIApplication对象,而且是单例。
  3. 一个iOS程序启动后创建的第一个对象就是UIApplication对象。
  4. 通过UIApplication *app = [UIApplication sharedApplication];可以获得这个单例对象。
  5. 利用UIApplication对象能进行一些应用级别的操作。

UIApplication详细讲解(//TODO:链接)

五、结语

至此,UIKit控件的简要介绍就结束了,链接的内容可以慢慢阅读,或者作查询用。

附上UIKit框架更完整的继承图:

六、参考文章:

iOS 的离屏渲染

UI相关:事件传递,图像显示,性能优化,离屏渲染