SwiftUI基础——创建并组合视图
Swift UI 基础
今天开 2020 年的 WWDC 了,所以来学习一下 Apple 的 app 开发吧。😜
翻译自:Learn to Make Apps with SwiftUI
英文原文:https://developer.apple.com/tutorials/swiftui/tutorials
这篇文章是 Apple 给的 SwiftUI 官方教程的一部分,我自己阅读学习的时候顺便翻译的。
创建并组合视图
原文链接:https://developer.apple.com/tutorials/swiftui/creating-and-combining-views
这个教程教你构建一个 iOS app —— Landmarks。这个 App 是用来发现、分享你喜欢的地点的。我们会从构建一个显示地标(Landmarks) 详情的 View 开始。
要布局这个地标详情的 View,我们用 stacks 来组合、堆放[^1] 图片和文本视图组件。我们还要用引入 MapKit 组件来提供地图视图。在你修改视图的时候,Xcode 会给你及时的反馈,让你看到改变的代码。
下载项目文件,跟着下面的步骤开始构建工程吧:
预计用时:40 分钟
Xcode 11:https://itunes.apple.com/us/app/xcode/id497799835?mt=12
[^1]: combine and layer,我的理解是 combine 在同一水平面组合,layer 做垂直方向上堆放
§ 1 创建新项目和探索画布
在 Xcode 创建一个使用 SwiftUI 的新项目。在里面探索画布、预览和 SwiftUI 的模版代码。
要在 Xcode 里预览视图,并与之交互,需要 macOS Catalina 10.15。[^2]
Step 1. 打开 Xcode,然后在 Xcode 的启动页面点击 Create a new Xcode project,或者选择 File > New > Project。[^3]
Step 2. 在模版选择窗口里选择 iOS 平台、Single View App 模版,然后点击 Next。
Step 3. 在 Product Name 处输入“Landmarks”,user interface 选择 SwiftUI,点击 Next。然后选择一个位置在你的 Mac 上保存这个项目。
Step 4. 在项目导航栏(Project navigator)里选择ContentView.swift
。默认情况下,SwiftUI 声明了两个结构体,第一个遵循了 View 协议,是用来描述视图的内容和布局的;第二个结构体声明了对这个视图的预览。
Step 5. 在画布(canvas)里点击 **Resume ** 来显示预览。
Tips:如果画布没有显示,你可以选择Editor > Editor and Canvas,来打卡它。
Step 6. 在 body 属性里,把 “Hello World”
改成对你自己打招呼的话。
在你改变视图体(a view’s body
property)的时候,预览及时地反映出了你的改变。
完成这一节后,我们的主要代码看上去是这样的:
1 | struct ContentView: View { |
【译注】:由于本人使用 MacOS Mojave 10.14,所以无法使用预览的功能。这还是相当痛苦的,没有预览几乎难以进行 UI 设计了,所以我采取的代替方案是在 Playground 中写 SwiftUI。实现的步骤如下:
新建一个 Playground,选择 iOS 平台,Blank 模版。打开 Playground 后,把里面的代码替换如下:
1 | import SwiftUI |
运行 Playground 至第 10 行(鼠标移到第十行的行标处,数字变成表示运行的箭头▶️,点击这个箭头),即可看到预览:
这样勉强可以进行 SwiftUI 的开发,只是无法如在 Catalina 的 Xcode 项目里那样和画布交互。
[^2]: 本人使用 MacOS Mojave 10.14,所以无法使用预览的功能,我采取的代替方案是在 Playground 中写 SwiftUI:
[^3]: 分享一个让我很感动的细节,Apple 的文档里,居然对这样的一张截图写了如此详细的 alt 说明: 扪心自问,我从来不认真写 alt,总认为这东西又看不见写它干嘛!看到了 Apple 的做法,不禁思索,万一正在阅读你的文章的人因为网络、计算机、甚至是视觉问题无法看到这张图片,我们难道没有义务为这样的特殊人群写一段图片说明嘛?尤其是对有视觉障碍的人士!以后我会尽可能认真写 alt!第一步就是——这篇文章翻译里包括这些 alt!
§ 2 定制文本视图(Text View)
原文链接: https://developer.apple.com/tutorials/swiftui/creating-and-combining-views#customize-the-text-view
通过修改代码,你可以定制视图的显示效果。当然,你也可以用检视器(inspector)来发现你可以做些什么,并帮助你完成代码。
在构建 Landmarks app 的过程中,你可以随意使用任何编辑器:代码编辑器(source editor),画布(canvas),检视器(inspectors)。无论你使用了那种方式来编辑,代码总会自动保持更新。
接下来,我们将使用监视器来定制文本视图。
Step 1. 在预览中,按住 command 点击我们写了问候的文本,会出现一个结构编辑弹出框(popover)。在这个弹出框里,选择 Inspect。
这个弹出框里还有其他的你可以定制的属性,不同的视图可能有不同的属性。
Step 2. 在检视器中,把文本(Text)改成 “Turtle Rock” —— 你的 app 中要展现的第一个地标。
Step 3. 把字体修改成 Title (标题)。
这个操作是让文本使用系统字体,这种字体可以跟随用户当前的偏好设置。
【译注】也可以使用代码完成这个更改: Text("Turtle Rock").font(.title)
。哈哈,这种写法就很 Swift,具体这是什么意思下面的原文有写。
要用 SwiftUI 定制视图,我们可以调用称作修饰器(modifiers)的方法。修饰器会包装一个 View 来更改其显示效果或其他属性。每个修饰器都会返回一个新的 View,所以我们常链式调用多个修饰器以叠加效果。
Step 4. 手动修改代码,添加一个修饰器:foregroundColor(.green)
,这是用来把文本颜色改成绿色的:
代码才是我们的视图效果的依据,当你用检视器来修改或移除修饰器时,Xcode 都会立刻在代码里做出相应的修改。
Step 5. 现在,我们在代码里按住 Command 点击 Text
的声明,从弹出框里选择 Inspect,这样也可以打开监视器。在 Color 菜单里,把颜色设置成 Inherited,这样文本的颜色就复原了。
Step 6. 注意,完成刚才那一步之后, Xcode 自动帮我们把代码做了相应的修改,即去掉了修饰器 foregroundColor(.green)
:
完成这一节后,我们的主要代码看上去是这样的:
1 | struct ContentView: View { |
§ 3 用 Stacks 组合视图
在上一节我们完成了一个标题。现在,我们将再添加一些文本视图来描述地标的详细信息,比如所处的公园名称和所在的州。
在使用 SwiftUI 创建 View 时,我们在视图的 body
属性里描述其内容、布局、行为。但是,body 属性只会返回一个视图。所以,我们要把多个视图放到 stack 里来让它们在水平、竖直、前后方向上组合在一起。
在这一节里,我们我们将用一个竖直方向的 stack 来把标题放在一个包含了地标细节的水平 stack 的上方。
(emmm,我一直不太会翻译这种比较长的英文句子,可能是我语文学的不好吧,我不知道怎么解耦这种有嵌套结构的句子)
我们 Xcode 的结构编辑(structured editing)提供把一个 view 嵌入一个容器视图、打开检视器和其他许多有用的操作。
Step 1. 按住 Command 点击文本视图的初始化器(构造函数啦),会显示出结构编辑弹出框,在里面选择 Embed in VStack(嵌入VStack)。
接下来,我们将通过从库里拖一个 Text 放到 Stack 里增加一个文本视图。
Step 2. 点击位于 Xcode 窗口右上方的加号(+)打开库。从里面拖一个 Text 出来放到我们的“Turtle Rock”文本视图代码的后面。
Step 3. 把新的文本视图的内容替换为“Joshua Tree National Park”。
接下来我们还会定制刚刚添加的那个表示位置的文本视图,以匹配所需的布局。
Step 4. 设置位置文本的字体为 subheadline(副标题)。
1 | Text("Joshua Tree National Park") |
Step 5. 编辑 VStack 的初始化器来指定视图前缘对齐(align the views by their leading edges,就是把左边对齐了,Android 里的 gravity="start"
这种意思吧)。
默认情况下,stack 会沿其轴线把内容中心对齐,并且只提供适合内容的空间。
接下来,我们要在表示位置的文本右边增加一个文本,表示其所在的州。
Step 6. 在画布中,按住 Command 点击 Joshua Tree National Park,选择 Embed in HStack(嵌入HStack)。
Step 7. 在位置后面新建一个文本视图,把内容改成州名,把字体设置为 subheadline。
Step 8. 为了让我们的布局占满整个设备的屏幕宽度,我们在包含公园和州名的 HStack 里添加一个 Spacer 来把两个文本视图分开。
Spacer
用来“撑大”容器布局,使其占满整个水平或竖直方向的父视图(its parent view)空间,而不再只是恰好包含内容。
Step 9. 最后,使用 padding()
修饰方法给地标的名称和细节增加一点空间。
完成这一节后,我们的主要代码看上去是这样的:
1 | struct ContentView: View { |
§ 4 创建自定义图片视图(Custom Image View)
现在,咱们的地标名称、位置都已经安排好了。下一步,我们打算加一张地标的图片。
我们将创建一个新的自定义视图,而不是直接在现在的文件里继续写更多的代码了。这个自定义视图将包含有遮罩、边框和投影。
首先,我们要先添加图片到项目的资产目录( asset catalog)。
Step 1. 把 turtlerock.png
(在Apple给的项目文件里的 Resources 文件夹里有)把它拖到 资产目录 编辑器里,Xcode 会为这个新图片创建一个图片集。
【译注】那个图片就是下面这张👇
接下来,我们要创建一个新的 SwiftUI View 来写我们的自定义图片视图。
Step 2. 选择 File > New > File,打开模版选择器,在 User Interface 中,选择 SwiftUI View,然后点 Next,把文件名取成 CircleImage.swift
,然后点 Create。
现在,我们就准备好插入图片,然后修改显示效果来达到我们的预期目标了。
Step 3. 用我们刚才准备好的图片来替换新建的文件里的文本视图,用 Image(_:)
来显示一张图片。
【译注】在 Playground 里写 Image("turtlerock")
要不成,要用 Image(uiImage: UIImage(named: "turtlerock.jpg")!)
Step 4. 添加一个 clipShape(Circle())
调用来把图片裁剪为圆形。
Circle
类型是一个形状,你可以把它当遮罩(mask)用,也可以把它作为一个圆形的视图(有圆形的笔触或者圆形填充的)。
Step 5. 再创建一个新的灰色笔触的圆,然后把它作为 overlay
加到我们之前的 Image 上,这样可以作出一个边框效果。
Step 6. 接下来,添加一个半径为 10 点的阴影。
Step 7. 把边框颜色改成白色。
现在,我们的自定义图片视图就完成了。
完成这一节后,我们的主要代码看上去是这样的:
1 | struct CircleImage: View { |
§ 5 同时使用 SwiftUI 和 UIKit
现在,咱打算做一个地图视图了。我们可以用一个来自 MapKit
的 MKMapView
类来提供一个地图视图。
要在 SwiftUI 中使用 UIView
的子类,我们要把它用一个实现了 UIViewRepresentable
协议的 SwiftUI 视图包裹起来。SwiftUI 也对 WatchKit 和 AppKit 的视图提供了相似的协议。
首先,我们要自定义一个视图来包装 MKMapView。
Step 1. 选择 File > New > File,打开模版选择器,在 User Interface 中,选择 SwiftUI View,然后点 Next,把文件名取成 MapView.swift
,然后点 Create。
Step 2. 添加一个 import 语句来导入 MapKit
,然后让 MapView
遵循 UIViewRepresentable
协议:
1 | // MapView.swift |
别介意 Xcode 提示的错误,我们将在接下来几步里解决这些问题。
接下来我们要完成 UIViewRepresentable
要求两个实现:
makeUIView(context:)
:这个方法用来创建 MKMapView;updateUIView(_:context:)
:这个方法用来设定视图,并对任意改变作出响应。
Step 3. 把新建的视图里的 body 属性的代码替换成定义一个 makeUIView(context:)
方法,这个方法用来创建并返回一个新的 KMapView
。
1 | struct MapView: UIViewRepresentable { |
Step 4. 在 MapView 里添加一个 updateUIView(_:context:)
方法来设置我们的地图把要显示的位置放到中心位置。
1 | func updateUIView(_ uiView: MKMapView, context: Context) { |
由于在静态模式(static mode)下预览只会渲染 SwiftUI 视图,而我们的地图是个 UIView 的子类,所以我们需要把预览调到 live 模式(live preview)才能看见地图。
Step 5. 点击 Live Preview 按钮来把预览调至 live 模式。然后,你可能还需要点击 Try Again 或者 Resume 按钮才能显示出预期的效果。
现在,我们将看见一张显示着 Joshua Tree National Park 的地图——这就是我们的地标 Turtle Rock 的家。
这一节里,我们完成的主要代码看上去是这样的:
1 | struct MapView: UIViewRepresentable { |
§ 6 组成 Detail 视图
现在,我们已经构建完了我们需要的一切——地标的名字和位置、一张圆形的图片以及其所在位置的地图。
现在,我们要把这些我们写好的自定义视图组合在一起,来完成一个地标的 Detail 视图的最终设计了!我们要用到的还是已经开始熟悉的那些工具。
Step 1. 在项目导航(Project navigator)中,选择打开 ContentView.swift
文件。
Step 2. 把之前那个包含三个 Text 的 VStack 用另一个新的 VStack 包裹起来。
Step 3. 把我们自定义的 MapView 放到 stack 的顶部,并用 frame(width:height:)
来调整其大小。
当你只指定了 height(高度)属性时,视图会自动把宽度设置成符合内容的。在这里,MapView 会拓展开充满整个可用空间。
Step 4. 点击 Live Preview 按钮来查看在组合视图中渲染出来的地图。
在显示 Live Preview 的同时,你也还可以继续编辑视图。
Step 5. 在 stack 里添加一个 CircleImage。
Step 6. 我们要把图片视图移到地图视图的上层:给它竖直偏移(offset) -130 点,底部内补(padding) -130 点。
这个调整通过把图片向上移,来提供给文本更多空间。
Step 7. 在最外层的 VStack 的底部增加一个 spacer,把我们的那些内容推到屏幕顶部。
Step 8. 最后一步,为了能让我们的地图拓展到屏幕顶部边缘(说直白点就是伸到刘海里😂),给地图视图加一个 edgesIgnoringSafeArea(.top)
修饰。
这一节里,我们完成的主要代码看上去是这样的:
1 | struct ContentView: View { |