Day17 – 创建一个测试项目

本文基于 Start Developing iOS Apps (Swift) 这个官网的学习文档,开始我们的 Swift UI 的绘制之旅。

我们的目标是基于 UIKit
框架来实现 SwiftUI Tutorials 的中的示例 APP  Landmarks

虽然对于初学者来说学习 SwiftUI 可能更加便捷,但目前国内的主流还是 UIKit
绘制 iOS 界面。

创建新项目 Landmarks

打开 Xcode,在 Xcode 的启动窗口点击 Create a new Xcode project
,或者选择文件 > 新建 > 项目。

在模板选择器中,选择 iOS 作为平台,选择 App 模板,然后单击 Next

产品名称输入 Landmarks
,界面选择  Storyboard
,生命周期选择  UIKit APP Delegate
,点击  Next
。在 Mac 上选择一个位置保存你的项目。

熟悉 Xcode 工作区域

Xcode 在 工作区窗口
中打开这个新项目。让我们花点时间熟悉Xcode工作区的主要部分:

  • Navigator area

    提供对项目各个部分的快速访问。

  • Editor area

    编辑源代码、用户界面和其他资源。目前看到的是项目的配置方面,目前,我们还不需要做任何更改

  • Utility area

    提供有关选定项目的信息以及对现成资源的访问。

  • Toolbar

    用于构建和运行应用程序,查看运行任务的进度以及配置工作环境。

不要纠结这些区域的细节,每个区域在真正需要使用时都会有详细的说明。

运行 iOS 模拟器

项目基于 Xcode 模板,所以基本应用程序环境会自动设置。即使没有编写任何代码,也可以构建和运行单视图应用程序模板,而无需任何其他配置。我们开始让我们的 APP 在 iOS 模拟器上运行起来。
我们按照下边几个步骤,将当前应用程序在 iOS 模拟器上跑起来

  • 在 Xcode 工具栏的 Scheme 弹出菜单中,选择 iPhone 12 Pro Max
  • 点击左边的运行按钮,或者使用快捷键  Command + R
    ,或者是菜单栏中 Product 中选择 Run
  • Xcode 在工具栏中间的活动查看器中显示有关构建过程的消息。Xcode 完成构建项目后,模拟器将自动启动。第一次启动可能需要一些时间。

当然,现在是一个空白页面,因为我们还没有任何处理。

查看默认模板文件

让我恩将目光聚集在导览区域( Navigator area
),在左上角  Navigator select tabs
区域选择文件夹图标——项目导航器( Project navigator
)。项目导航器显示项目中的所有文件。如果项目导航器未打开,请单击导航器选择器栏中最左侧的按钮。或者,选择  View
Navigators
Projiect

Single View 应用程序模板附带了几个用于设置应用程序环境的源代码文件。让我们依次来做一个了解。
点击任意文件,Xcode 会在窗口的主编辑器区域打开源文件。

AppDelegate.swift 和 SceneDelegate.swift

在 Xcode 11之前,当一个新项目创建时,项目的默认文件中只有 AppDelegate.swift,在 Xcode 11 之后创建了一个名为 SceneDelegate.swift 的新文件。
无论你是否接触过 Xcode 11 之前创建过 iOS 项目,我们都应该对这一改变有一个基准的认识。熟知 iOS 的变更也是一名合格 iOSer 需要掌握的能力。
在 Xcode 11 之前建立的所有应用程序中,AppDelegate 是应用程序的主入口,它是许多逻辑和应用程序状态将被处理的地方。它是应用程序启动和应用程序前台和后台逻辑处理的地方。从 Xcode 11 开始,原来 AppDelegate 的职责被划分为 AppDelegate 和 SceneDelegate。这是 iPad-OS 引入新的支持多窗口特性的结果,该特性将 AppDelegate 的工作分为两个。
下面是官网对 AppDelegate.swift 和 SceneDelegate.swift 职责的总结

The AppDelegate will be responsible for the application lifecycle and setup. The SceneDelegate will be responsible for what is shown on the screen (Windows or Scenes) handle and manage the way your app is shown.

AppDelegate 管理应用程序的设置与生命周期,SceneDelegate 则专注于管理其在设备上的界面展示

AppDelegate.swift 仍然是应用程序的主入口,也是应用程序的主配置项。

import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 在启动应用程序和完成应用程序设置时调用此方法。
return true
}

// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// 当应用程序需要新的场景或窗口来显示时,将调用此方法。在应用启动时不会调用此方法,只有在需要获取新场景或新窗口时才调用此方法
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}

func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
// 当用户废弃场景时(例如通过从多任务窗口中滑动场景)或以编程方式废弃场景时,将调用此方法
}
}

默认情况下 AppDelegate 主要提供三个方法,两个用来管理 UISceneSession的生命周期。除了这些方法,AppDelegate还可以处理外部服务,如推送通知注册、位置服务、应用终止等。

值得注意的是, AppDelegate 管理的是 UISceneSession
,默认情况下,应用程序有且只有一个场景,只有涉及到多场景应用才可以,才会触及到  UISceneSession Lifecycle

前文我们说过,之所以出现这种拆解过程是因为 iPad-OS 引入新的支持多窗口特性的结果。这对我们在工程化和组件化时也有启示。
从 Xcode 11 开始新建的 Swift 工程, ScenedLegate承担起原 AppDelegate 的一些职责——尤其是与UIWindow相关的。SceneDelegate 负责 UI 和数据在屏幕上的显示内容。
从其默认的代码片段来看:

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// 这是UISceneSession生命周期中调用的第一个方法。这个方法将创建新的 UIWindow,设置 RootViewController,并使这个窗口成为要显示的KeyWindow。
guard let _ = (scene as? UIWindowScene) else { return }
}

func sceneDidDisconnect(_ scene: UIScene) {
//这是所有方法中最有趣的方法。当场景被发送到后台时,iOS可能会决定完全丢弃场景以释放资源。
//这并不意味着应用程序被杀死或不运行,但只是场景从会话断开,不活动。
//iOS 可以决定重新连接这个场景到场景会话当用户把这个特定的场景再次带到前台时。此方法可用于丢弃任何不再使用的资源。
}

func sceneDidBecomeActive(_ scene: UIScene) {
// 这个方法在 sceneWillEnterForeground 方法之后被调用,当前场景已经设置好了,并且是可见的、可使用的。
}

func sceneWillResignActive(_ scene: UIScene) {
// 当前场景并不是可见可使用的,例如,电话,拉起控制中心等,点击进入后台也会调用这个方法,例如查看当前的后台任务
}

func sceneWillEnterForeground(_ scene: UIScene) {
// 当场景即将开始时(例如,应用程序首次激活时)或从后台到前台过渡时,将调用此方法。
}

func sceneDidEnterBackground(_ scene: UIScene) {
//程序进入后台,一定是完整进入后台,如果是唤起后台任务则不会调用这个方法
}
}

SceneDelegate 的职责就是负责响应一个场景的显示内容和场景的生命周期。

在实际应用过程中我们可以关注一下 sceneDidDisconnect
方法的使用。在笔者关于 OOM 的文章中我们知道,当程序在后台时有可能会被 iOS 杀掉以便新的程序的使用。当你的程序可以在后台释放某些资源时,可以在这里处理。

ViewController.swift

然后我们将目光聚焦在 ViewController
文件,也就是默认情况下的应用程序的  RootViewController
ViewController
是继承于  UIViewController
的子类。

默认模板创建的 ViewController
里面只有一个关于  ViewController
生命周期方法—— viewDidLoad

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}

}

关于 iOS 的 ViewController
部分值得我们引申一些了解。

Window 和 ViewController

UIViewController
是管理   UIKit
应用程序  视图层次结构
的对象。
其大致关系如下图所示:

视图结构虽然不一定直接加载在 ViewController
上——有可能是直接加载到  Window
上。但是默认的  KeyWindow
会关联一个 RootViewController
。对于单场景的应用程序,根视图控制器是场景的  storyboard
的初始视图控制器。

常见的页面状态

除了 viewDidLoad
方法以外,常见的  ViewController
还包括

  • func viewWillAppear(Bool)

  • func viewDidAppear(Bool)

  • func viewWillDisappear(Bool)

  • func viewDidDisappear(Bool)

当其视图的可见性更改时,视图控制器会自动调用其自己的方法,以便子类可以响应此更改。我们经常会在 Appear
方法中执行一些诸如添加通知类的操作,而在 Disappear
方法中移除这些通知。

Main.storyboard

然后打开我们的 Main.storyboard
文件,在 Xcode 的编辑区域


这个页面我们主要关注 4 个模块

ViewController

该部分是页面的布局区域,这里值得注意的是 Safe Area
部分,基本上我们后续加的 SubView 部分都是在  Safe Area
里面。这个是单一页面的画布,使用画布来添加和排列用户界面元素。

StoryboardEntryPoint

默认模板中 ViewController
作为 Window 的根视图,我们通过修改这个部分的视图指向,为 Window 设置新的根视图,或者是某个  storyboard
所有后续视图的入口。

画布区域

当您在 iPhone 11模拟器应用程序中运行应用程序时,此场景中的视图就是你在设备屏幕上看到的。但是,画布上的场景可能与模拟器屏幕的尺寸不同。可以在画布底部选择屏幕大小和方向。
即使画布显示了一个特定的设备和方向,创建一个自适应的界面也很重要——一个自动调整的界面,使它在任何设备和方向上都适配的很好。
在开发界面时,可以更改画布的视图,看到界面如何适应不同大小的屏幕。

AutoLayout

为了实现一个自动调整的界面,我们使用约束加载区域的方法为我们的界面添加视图。

Assets.xcassets

然后打开我们的 Assets.xcassets
,在 Xcode 的编辑区域

一般我们使用 Assets.xcassets
管理我们程序的图片文件,当然还可以管理应用程序中的 颜色
。我们可以定义程序的主题颜色,并且直观看到具体的色值。

开始开发

当我们完成对初始化项目的概览以后,我们开始准备创建我们的项目,开始我们的目标是完成下面的界面。