iOS 视图

view (UIView 对象或 UIView 的子类)知道怎么把自己绘制到界面的矩形区域中。由于 views 你的 app 才有一个可见的界面。创建和配置一个 view 可以极其简单, 例如你可以拖拽一个界面对象, 比如一个 UIButton 到 nib 编辑器的 view 中; 当 app 运行时, 按钮出现, 没什么问题。但是你也可以以强大的方式操作 views, 实时地。你的代码可以做某些或全部的描绘视图自己, 并展示其它物理变化, 还可以带有动画。

view 也是一个响应器(responder)(UIView 是 UIResponder 的子类)。这意味着 view 能经受用户交互, 例如轻敲和重击(taps and swipes)。因此, views 不仅是用户看见的界面的基础, 还是用户触摸的界面的基础。组织好你的 views 以使正确的视图对给定的触摸有反应。

视图层级(view hierarchy)是视图组织的主要模式。 视图可以有子视图; 子视图只有一个直接的父视图。因此会形成一个视图树。这个层级允许视图可以一块儿出现和消失。如果从界面中移除一个 view, 那么这个 view 的子视图也会被移除; 如果隐藏一个 view(使不可见), 那么它的子视图也是隐藏的; 如果移除一个 view, 那么它的子视图也随之移除; 并且 view 中的变化同样也被子视图共享。视图层级也是响应链的基础。

view 可能来自一个 xib, 也可以用代码创建。 为了平衡, 任何一种方法也不会比另一种更好; 它取决于你的需要和喜好, 还有你的 app 的总体架构。

The Window


视图层级的最上面是 app 的 window(窗口)。它是 UIWindow 的一个实例, 即 UIView 的一个子类。你的 app 应该只有一个主窗口(one main window)。 主窗口在 app 启动时被创建并且不能被摧毁或代替。主窗口也组成了背景, 是你所有其它可见视图的最终的父视图。其它 views 由于作为你的 app 窗口的子视图, 以某种深度, 而可见。

如果你的 app 可以在额外的屏幕上展示视图, 你需要创建额外的 UIWindow 来包含那些视图

app 的窗口必须在最开始时填满(fill)设备的屏幕。在窗口实例化时通过把 window 的 frame 设置为屏幕的 bounds 时确保了填满整个屏幕。如果你使用的是 main storyboard, UIApplicationMain 函数会在 app 启动时自动在幕后为你处理; 但是没有 main storyboard 的 app 也是可以的, 不过你得在 app 生命周期的早期自己创建 window 并设置它的 frame, 就像这样:

1
let w = UIWindow(frame: UIScreen.mainScreen().bounds)

在新的 iOS 9 中, 不使用 frame 来实例化UIWindow 也绰绰有余; 会为你把屏幕的 bounds 赋值给 window 的 frame 的:

1
let w = UIWindow()

window 必须也和 app 的生命期一样持久。为了做到这一点, app 的代理类(delegate class)拥有了一个带有强保留(strong retain)策略的 window 属性。当 app 启动时, UIApplicationMain函数实例化那个 app 代理类并保留(retain)那个结果实例。这就是 app 代理实例(the app delegate instance); 它绝对不会释放, 所以它持久到 app 的整个生命周期。然后该 window 实例被赋值给 app 代理实例的 window 属性; 因此 window 也持久到 app 的整个声明周期。

通常你不会手动并直接地在你的 main window 中存放任何视图内容。相反, 你会获得一个视图控制器并把它赋值给 main window 的 rootViewController 属性。 再一次地, 如果你正在使用 main stroyboard, 这会在幕后自动为你做好; 提到的那个视图控制器会成为你的 storyboard 的初始视图控制器。

当一个视图控制器变成 main window 的 rootViewController 时, 它的主视图(它的 view)成为你的 main window 仅有的一个直接子视图 — 该 mian window 的根视图(root view)。你的 main window 中的所有其它视图成为根视图的子视图。因此, 在视图层级中用户通常看到的最高等级的对象就是根视图。某种情况下, 用户可能有机会瞄到根视图后面的 window; 基于这个原因, 你可能想给 main window 设置一个可行的背景色(backgroundColor)。但这看起来不见得, 并且通常你没有理由去改变 window 自身的任何东西。

app 的界面直到包含 app 的 window 成为该 app 的 key window 时才可见。这通过调用 UIWindow 的实例方法 makeKeyAndVisible 来完成。

让我们总结一下初始化创建, 配置, 和 main window 的展示是怎么发生的。有 2 种情况需要考虑:

  • 带有 main storyboard 的 app

如果你的 app 拥有一个 main storyboard, 它在 Info.plist key 的 “Main storyboard file base name” (UIMainStoryboardFile)中指定 — 所有的 Xcode 7 app 模板中都是默认好的 — 然后 UIApplicationMain 实例化 UIWindow, 正确地设置它的 frame, 并把那个实例赋值给 app delegate 的 window 属性。此外, 它初始化了 storyboard 的初始视图控制器, 并把那个实例赋值给 window 的 rootViewController 属性。所有这些都是发生在 app delagate 的 application:didFinishLaunchingWithOptions: 被调用之前。

最后, UIApplictionMain 在 window 上调用 makeKeyAndVisible, 来展示你的 app 界面。这反过来自动让根视图控制器获取到它的主视图(通常从 nib 中加载), 其中 window 把它添加为它自己的根视图。这发生在 application:didFinishLaunchingWithOptions 被调用之后。

  • 不带 main storyboard 的 App

如果你的 app 不带 main storyboard, 那么 window 的创建和配置必须另辟蹊径。通常会使用代码。没有 Xcode 7 的 app 模板缺少 main storyboard, 但是假设你以一个 Single View Application 模板开始, 你可以按照下面的试验:

  1. 编辑 target。 在 General 面板, 选中 Main Interface 栏中的 “Main” 并删除(按下 Tab 来使该更改生效)。
  2. 从工程中删除 Main.storyboardViewController.swift
  3. 删除 AppDelegate.swift 中的全部内容

现在你拥有了一个含有一个 app target 但是没有 storyboard 和 代码的工程。为了让这个最小化的 app 能够工作, 你需要以这种方式编辑 AppDelegate.swift 以使用足够的代码在 app 启动时来创建和展示 window, 就像 Example 1-1 展示的那样。

Example 1-1 不带 storyboard 的 app delegate 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import UIKit

@UIApplicationMain
class AppDelegate : UIResponder, UIApplicationDelegate {
var window : UIWindow?
func application(application: UIApplication,
didFinishLaunchingWithOptions lanuchOptions: [NSObject : AnyObject]?)
-> Bool {
self.window = UIWindow()
self.window!.rootViewController = UIViewController()
self.window!.backgroundColor = UIColor.whiteColor()
self.window!.makeKeyAndVisible()
return true
}
}