UIView解析

UIView概述

UIView 是所有视图的根类,定义了所有视图的公共行为,属于 UIKit 框架。

UIView是App界面的基本构件块,它在屏幕上定义了一个矩形区域,并处理该区域中的绘图和触摸事件,是用户和App的主要交互方式。

使用 UIView 可以执行如下操作:

  1. 绘制内容和动画:UIView 使用 Core GraphicsOpenGL ESUIKit 之类的图形技术 在其矩形区域中绘制形状、图像和文本。而 GLKView 使用的是 OpenGL ES进行绘制。

  2. 布局和管理子视图:父View负责子View的位置和调整大小;管理子视图的列表

  3. 事件处理(Event Handling Guide for iOS):接收触摸事件;参与响应者链

  4. 支持拖放交互

  5. 对焦点变化做出反应

  6. 对视图的大小、位置和外观属性进行动画

UIView 与核心动画层(Core Animation layers)一起工作,处理视图内容的渲染和动画。

UIKit 中每一个 UIView 由一个 CALayer 对象支持,该对象管理视图的后台存储(backing store),并处理视图相关的动画。

我们执行的大多数操作都应该通过 UIView 接口进行。如果需要对 UIView 的渲染或动画行为进行更多控制,一般通过其图层执行操作。

用户交互式更新页面的过程:

1、用户触摸屏幕
2、硬件向UIKit框架报告触摸事件。
3、UIKit框架将touch打包到UIEvent对象中,并将其分派到合适的视图中。(Event Handling Guide for iOS)
4、视图的事件处理代码对事件作出响应。你的代码可能:

  • 改变视图或其子视图的属性(frame、bounds、alpha等)。
  • 调用setNeedsLayout方法将视图(或其子视图)标记为需要更新布局。
  • 调用setNeedsDisplaysetNeedsDisplayInRect:方法将视图(或其子视图)标记为需要重新绘制。
  • 通知控制器某些数据的更改

5、如果视图的几何图形因任何原因发生更改,UIKit将根据以下规则更新其子视图:

  • 如果为视图配置了自动调整大小规则(autoresizing rules),UIKit会根据这些规则调整每个视图。
  • 如果视图实现layoutSubviews方法,UIKit将调用它。
    6、如果任何视图的任何部分被标记为需要重绘,UIKit会要求视图重绘自己。
    对于显式定义drawRect:方法的自定义视图,UIKit调用那个方法。

7、任何更新的视图都将与应用程序的其他可见内容合成,并发送到图形硬件进行显示。

8、图形硬件将渲染的内容传输到屏幕。

CALayer 和 UIView 的关系

  • 在iOS中,每一个 UIView都有一个对应的 CALayer 对象支持
    layerUIView 提供基础结构,图层 layer 使视图内容的绘制 和 动画更加容易和有效,并在此过程中保持高帧率。

  • CALayerUIView 内部实现细节。
    CALayer 不能处理事件、绘制内容、参与响应者链或执行许多其他操作,这就需要 UIView 处理这些类型的交互。

  • iOS中,视图只是图层对象的薄薄包装。
    因此使用轻量级的 UIView 并没有显著的性能影响,这使得你既可以使用 CALayer 底层的特性,也可以使用UIView的高级API(如:自动布局、事件处理)。

  • 最好使用图层相关视图,而不是创建独立的图层关系,因为视图可以处理复杂的事件。

UIViewCore Animation layers 结合使用,以处理视图内容的渲染和动画处理。核心动画层对象的使用对性能具有重要意义。尽可能少地调用视图对象的实际绘图代码,并且在调用该代码时,结果由 Core Animation 缓存并在以后尽可能多地重用。重用已经渲染的内容消除了更新视图通常所需的昂贵绘制周期。在可操作现有内容的动画过程中,此内容的重用尤其重要。这种重用比创建新内容高效得多。

UIView 的属性和方法

frame、bounds、center

  • frame:
    1、表示当前视图在父视图坐标系中的位置和大小,以父控件的左上角为坐标原点。
    2、view 有 transform,则 frame 不能反映其在父view中的实际位置,需要用 bounds+center 来反映。

  • bounds:
    1、 描述视图在自身坐标系中的位置和大小,主要在绘制过程中使用。
    2、bounds.origin代表自己坐标系的原点,为其 subViews 提供的坐标系。x < 0 向右,y < 0 向下。
    3、bounds.size 默认和 frame.size 相等,修改它会相对于其中心点扩大或缩小视图,同时frame属性的size会随之匹配。

  • center
    表示当前视图中心点在其父视图中的位置

1
2
3
4
5
6
7
8
9
10
11
12
let pyView = UIView(frame: CGRect(x: 100, y: 200, width: 200, height: 200))
pyView.backgroundColor = UIColor.orange.withAlphaComponent(0.5)
pyView.bounds = CGRect(x: 50, y: 50, width: 200, height: 200)
view.addSubview(pyView)

let redView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
redView.backgroundColor = .red
pyView.addSubview(redView)

let blueView = UIView(frame: CGRect(x: 200, y: 200, width: 50, height: 50))
blueView.backgroundColor = .blue
pyView.addSubview(blueView)

UIKit中的默认坐标系的原点在左上角;Core GraphicsOpenGL ES 使用的坐标系的原点位于视图或窗口的左下角。

transform(仿射变换)

仿射变换(Affine Transform)是一种数学矩阵,它指定了一个坐标系中的点如何映射到另一个坐标系中的点。.

UIViewtransform 属性用于在其父视图坐标系内进行移动、缩放、旋转,这些变换操作是相对于UIView的 锚点发生的。若要更改锚点,需修改视图底层的CALayeranchorPoint属性。

1
var transform: CGAffineTransform { get set }
  • 在ios8.0及以后版本中,transform属性不会影响自动布局。
  • UIView进行多次transform,转换的顺序会影响最终结果。

alpha、isHidden、isOpaque

alphaisHidden 直接改变视图的不透明度。

如果你使用的UIView是用完全不透明的内容完全填充其边界矩形,则可设置isOpaque = true 来消除不必要的合成操作来提高性能。

isOpaque = true 会告诉UIKit它不需要在你的视图后面渲染任何内容,减少渲染可以提高绘图代码的性能。

Autoresizing(自动伸缩属性)

如下这些属性会影响View及其subView的自动调整大小行为:

1
2
3
4
// 控制视图如何响应其父视图边界的更改
var autoresizingMask: UIView.AutoresizingMask
// 控制是否调整当前视图的子视图的大小
var autoresizesSubviews: Bool

当对 view 采用自动布局时,需要设置view.translatesAutoresizingMaskIntoConstraints = false

contentMode 内容模式

UIView 第一次被显示时,它像往常一样呈现它的内容,结果被捕获在一个底层位图中。之后,对视图几何形状的更改并不总是会导致重新创建位图。相反,contentMode属性中的值决定了位图如何缩以适应新的边界。

当设置 view.contentMode = .redraw 时,会重新调用 draw(_:)。改变 frameboundscontentMode等属性,并不会调用draw(_:)

contentStretch伸缩拉伸

可以将视图的一部分指定为可拉伸的,当视图的大小更改时,仅可拉伸部分中的内容会受到影响。通常应用于UIButton、UIImage上。

只有当 contentMode 的值为:scaleToFill.scaleAspectFit.scaleAspectFill 时,才支持可拉伸视图。

1
2
3
4
// 在iOS7.0中被废弃,swift中无法使用。
@property(nonatomic) CGRect contentStretch
// 使用如下方法达到一样的效果
func resizableImage(withCapInsets capInsets: UIEdgeInsets) -> UIImage

1、拉伸图片

1
2
3
4
5
let imgView = UIImageView(frame: CGRect(x: 100, y: 100, width: 200, height: 160))
imgView.backgroundColor = UIColor.green
let img = UIImage(named: "chat")
imgView.image = img
view.addSubview(imgView)
1
2
3
let halfW = (img?.size.width ?? 0.0) / 2, halfH = (img?.size.height ?? 0) / 2
let resizeImg = img?.resizableImage(withCapInsets: UIEdgeInsets(top: halfH, left: halfW, bottom: halfH, right: halfW), resizingMode: .stretch)
imgView.image = resizeImg

2、平铺图片

1
2
3
4
5
let imgView = UIImageView(frame: CGRect(x: 100, y: 300, width: 300, height: 25))
imgView.backgroundColor = UIColor.blue.withAlphaComponent(0.5)
let img = UIImage(named: "sun_icon")
imgView.image = img
view.addSubview(imgView)
1
2
let tillImg = img?.resizableImage(withCapInsets: UIEdgeInsets.zero, resizingMode: .tile)
imgView.image = tillImg

contentScaleFactor

1
var contentScaleFactor: CGFloat { get set }

contentScaleFactor 表示视图的比例因子,决定如何将视图中的内容从逻辑坐标空间(以点为单位)映射到设备坐标空间(以像素为单位)。这个值通常是1.0或2.0。

此属性的默认值是与当前显示视图的屏幕相关联的比例因子。

通常,您不需要修改此属性中的值。但是,如果你的应用程序使用OpenGL ES绘图,你可能想要改变比例因子以牺牲图像质量来换取渲染性能。

isExclusiveTouch(处理事件)

1
var isExclusiveTouch: Bool { get set }

isExclusiveTouch 表示接收器是否独占方式处理触摸事件,默认值为false

如果其值为 true,则会导致接收器阻止将触摸事件传递到同一窗口中的其他视图。

添加子视图的方法

1
2
3
4
5
6
7
8
// 交换2个索引子视图的位置
view.exchangeSubview(at: 0, withSubviewAt: view.subviews.count - 1)

// 在 view 的子视图 greenView 上方插入 redView
view.insertSubview(redView, aboveSubview: greenView)

// 在 view 的子视图 yellowView 下方插入 greenView
view.insertSubview(greenView, belowSubview: yellowView)
1
2
3
4
5
// 把 yellowView 放到 view 的子视图最顶部显示
view.bringSubviewToFront(yellowView)

// 把 greenView 放到 view 的子视图最底部显示
view.sendSubviewToBack(greenView)

添加子视图的变化

1
2
3
4
5
6
func willMove(toSuperview newSuperview: UIView?)
func willMove(toWindow newWindow: UIWindow?)
func willRemoveSubview(_ subview: UIView)
func didAddSubview(_ subview: UIView)
func didMoveToSuperview()
func didMoveToWindow()

坐标转换

1
2
3
4
5
6
7
// 将当前视图的本地坐标系的坐标转(方法调用者)换为指定视图(传入的View参数)的坐标系统
open func convert(_ point: CGPoint, to view: UIView?) -> CGPoint
open func convert(_ rect: CGRect, to view: UIView?) -> CGRect

// 将其他视图的坐标系统(传入的View参数)转换为当前视图的本地坐标系(方法调用者)
open func convert(_ point: CGPoint, from view: UIView?) -> CGPoint
open func convert(_ rect: CGRect, from view: UIView?) -> CGRect

drawRect、layerClass

draw(_ rect:) 属于 UIViewRendering 协议中的方法。虽然该方法是UIView的,事实上是底层 的CALayer安排了重绘工作和保存了因此产生的图片。Apple不允许直接调用该方法,当需要更新绘制时,需要调用 setNeedsDisplay()

可以在UIView的子类中重写该方法,通过 Core Graphics 技术绘制视图内容,实现其绘制代码。该方法负责将视图的内容绘制到当前的图形上下文中,该上下文由系统在调用此方法之前自动设置。我们可以获得图形上下文的引用,但不要建立对图形上下文的强引用,因为图形上下文会更改。

如果使用 OpenGL ES 进行绘制,则应使用 GLKView 类而不是子类UIView。

注意自定义 UIView 时如果不进行绘制,不要在子类中写一个空 的-drawRect:方法,这会造成CPU资源和内存的浪费。

1
func draw(_ rect: CGRect)

重写layerClass方法,为UIView关联不同类型的CALayer。由于不同的CALayer类型有专门的功能,选择不同的层类可能使您能够以简单的方式提高性能或支持特定类型的内容。具体可查看:Different Layer Classes

1
2
3
4
5
6
override class var layerClass: AnyClass {
return CAShapeLayer.self
}

// layer 实例的类型由 layerClass 返回的值确定,并且 layer 实例的 delegate 为 UIView
var layer: CALayer { get }

布局方法

1
2
3
func setNeedsLayout()
func layoutIfNeeded()
func layoutSubviews()

其它属性方法

默认值为 false,当为true时,会对超出其显示范围的子视图的内容进行裁剪。

1
var clipsToBounds: Bool

是否为 view 的子视图

1
func isDescendant(of view: UIView) -> Bool

UIView的动画属性

UIView中可设置动画的属性

  • frame

  • bounds

  • center

  • transform:在2D空间中相对于其中心点缩放,旋转或平移视图

  • alpha:更改视图的透明度

  • backgroundColor

  • contentStretch:更改视图内容的拉伸方式。

有效使用视图的技巧

  • 最小化为UIView自定义绘图drawRect:,只有当现有的系统视图类不能提供所需的外观或功能时。

  • 充分使用contentMode内容模式,来缩放视图的现有内容以适应视图的框架矩形。

  • 尽可能将视图的不透明属性设置为isOpaque = true来提高性能

  • 在滚动时调整视图的绘制行为
    滚动可以在短时间内引发大量视图更新。如果没有适当地调整视图的绘制代码,视图的滚动性能可能会很缓慢。

  • 不要通过嵌入子视图自定义UIControl
    尽管从技术上可以将子视图添加到继承自UIControl的标准系统控件对象,但决不能以这种方式自定义它们。例如:通过在UIButton中嵌入自定义图像视图或标签来规避这些方法,可能会导致应用程序现在或将来某个时候(如果按钮的实现发生更改)行为不正确。

学习博客:

UIKit

Views and Controls

About Windows and Views

Core Animation

掘金

iOS的动态高度

文章作者: Czm
文章链接: http://yoursite.com/2020/09/10/UIView%E8%A7%A3%E6%9E%90/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Czm