这两天把 View Programming Guide 看了一遍,内容不算多也很基础,不过有些注意点和问题自己以前也困惑过,所以想总结一下。也顺便写了个 demo,方便理解。

Frame & Bounds & Center

你可以独立地更改frame,bounds,和 center,但改变其中一个会对其他的造成影响:

  • 当你设置了 frame 属性是,bounds 属性内的尺寸值会相应的适配框架矩形的尺寸值。在 center 属性内的值同样改更改适配新的框架矩形的中心点。
  • 当你设置了 center 属性,frame 内的原点值跟着改变。
  • 当你设置了 bounds 属性的尺寸,在 frame 属性内的尺寸值同样改变适配新的边界矩形的尺寸。

还有,默认情况下,子视图不会被它的父视图剪切。因此,任何位于父视图的框架外部的子视图都是以其整体呈现。你可以改变这个行为,通过设置父视图的 clipsToBounds 属性为 YES。无论子视图可见部分是否被剪切,触摸事件始终遵从目标的父视图的边界矩形。换句话说,在父视图边界矩形外部的那部分视图发生的触摸事件不会发出给视图。

contentMode

关于contentMode各个mode间的区别,这张图足以说明:

content_mode_overview

还有个就是UIViewContentModeRedraw,当设置为这个mode时,每一次你改变视图的几何状态时,都会调用drawRect:来响应视图的变化(如果你设置为其他mode的话,drawRect:只会第一次显示的时候调用,之后只会在已经显示的视图内容上进行变化,不会重新绘制视图)。因此设置这个mode的话会导致视图变化响应变慢,所以你应该尽量避免使用这个值,除非你想在你的自定义视图中以自己的方式来拉伸和调整尺寸。

Stretchable Views

文档中提到可以用通过设置contentStretch来使视图 stretchable,只是这个属性在 iOS6.0 的时候就被废除了(-_-!)。不过可以通过 UIImageresizableImageWithCapInsets: 来实现.

1
2
3
let image = UIImage(named: "stretchImage")!
let edgeInsets = UIEdgeInsetsMake(30, 30, 30, 30)
imageView.image = image.resizableImageWithCapInsets(edgeInsets, resizingMode: .stretch)

效果如下:
stretch_demo

你也可以直接在 Asset Catalog 里面选择图片,点击Show Slicing来设置:
set_slicing

更详细的设置你可以看下这篇 文章

Transform

UIView的 transform 属性提供了一种简单快速的方式来改变视图,但需要注意的一点就是,如果你对视图进行 transform 操作,那么 frame 属性的值是不可靠的,你只能使用 bounds(不会因为transform而改变) 和 center 来获取视图的尺寸和定位。

我在 demo 中写了个例子:对 imageView 进行 rotate 的操作,然后将它的frame赋给 grayView,从下图中就可以看出你得到的 ImageView 的 frame 并不是它实际上的位置信息。

frame_demo

External Display

之前没涉及过这块,就顺便写了个 demo 熟悉一下。

实现步骤:
1.add observer:

1
2
3
let defaultCenter = NotificationCenter.default
defaultCenter.addObserver(self, selector: #selector(handleScreenConnectNotification(_:)), name: .UIScreenDidConnect, object: nil)
defaultCenter.addObserver(self, selector: #selector(handleScreenDisconnectNotification(_:)), name: .UIScreenDidDisconnect, object: nil)

2.connect 后的设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
func handleScreenConnectNotification(notification: NSNotification) {
let extScreen = notification.object as! UIScreen
// no scaling will occur. use overscanCompensationInsets to determine the necessary insets to avoid clipping
extScreen.overscanCompensation = .none
let screenBounds = extScreen.bounds
secondWindow = UIWindow(frame: screenBounds)
secondWindow.screen = extScreen
// Set the initial UI for the window.
secondWindow.isHidden = false
}

你可以通过 UIScreenMode 来定义了单屏幕模式的属性。可以从它的 availableModes 属性获取屏幕支持的模式的列表并且遍历列表获取的一个来适配你的需求。

Tips for Using Views Effectively

  • 尽量少使用自定义绘制,更多的集成已有的视图控件。比如,UIButton 包含了设置标题和背景图像的方法,不应该再自定义嵌入 UILabel 和 UIImageView

  • 尽量不设置视图为透明。设置 opaque 为不透明可以更少的渲染内容,更加的高效。

  • 调整你的视图在滚动时的绘制行为。滚动时会导致短时间内产生大量的视图更新,如果你的绘制代码没有适当的应对,视图在滚动时的性能将会迟滞。所以应该考虑在滚动操作开始时更改你的视图行为。例如,你可以暂时降低内容渲染的质量或滚动过程中更改内容模式。当滚动停止后,你可以回到视图之前的状态和按需要更新内容。