01 Creating And Combining Views - ly918/SwiftUI-Chinese-Documents GitHub Wiki
SwiftUI纲要
创建和组织视图
本教程将指导您构建一个iOS应用程序——
Landmarks(地标),用于发现和分享您喜欢的地方。首先,您将构建显示Landmark的详情页面。为了布局视图,"地标"使用
stacks来对image、text等组件进行组织和分层。要将地图添加到视图中,需要包含一个标准的MapKit组件。当您优化视图的设计时,Xcode提供实时反馈,这样当您修改代码时,就可以看到视图状态的改变。下载项目文件开始构建此项目,并按照以下步骤操作:
学习时长:40分钟
第一节 创建新项目并浏览画布
创建一个使用SwiftUI的新Xcode项目。浏览画布、预览和SwiftUI模板代码。
要在Xcode中预览画布上的视图并与之交互,请确保Mac运行的是macOS Catalina 10.15。

步骤一
打开Xcode并在Xcode的启动窗口中单击Create a new Xcode项目,或者选择File>new>project。

步骤二
在模板选择器中,选择iOS平台,选择单视图应用程序模板,然后单击下一步。

步骤三
输入“Landmarks”作为产品名称,选中Use SwiftUI复选框,然后单击Next。选择一个路径来保存你的项目。

步骤四
在项目导航器中,单击以选择ContentView.swift。
默认情况下,SwiftUI视图文件声明两个结构。第一个结构实现了视图的必选协议,并描述了视图的内容和布局。第二个结构声明了该视图的预览。
ContentView.swift
import SwiftUI
//
struct ContentView: View {
var body: some View {
Text("Hello World")
}
}
//
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}

步骤五
在画布中,单击Resume以显示预览。
Tips:如果画布不可见,请选择
Editor > Editor and Canvas以显示它。

步骤六
在body属性中,将“Hello World”更改为自己的问候语。
当您在视图的body属性中更改代码时,预览将更新以反映您的更改。
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello SwiftUI!")
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}

第二节 自定义文本视图
您可以通过更改代码,或使用检查器查看可用的内容,并帮助您编写代码,来自定义视图的显示。
构建Lanmarks应用程序时,可以使用任意编辑器组合:源码编辑、画布或检查器。无论使用哪种工具,代码都会保持更新。

接下来,您将使用检查器自定义文本视图。
步骤一
在预览中,按住command并单击问候语以打开结构化编辑弹出窗口,然后选择Inspect。
弹出窗口显示可自定义的不同属性,具体取决于所检查的视图类型。

步骤二
使用检查器将文本更改为“Turtle Rock”,即您将在应用程序中显示的第一个地标的名称。

步骤三
将font更改为Title。
这会将系统字体应用于该文本,以便它正确适应用户系统偏好的字体大小和设置。

要自定义SwiftUI视图,可以调用名为
修饰符(modifier)的方法。修饰符会包装视图以更改其显示或其他属性。每个修饰符都返回一个新视图,因此通常采用垂直的链式调用多个修饰符。
步骤四
手动编辑代码添加foregroundColor(.green)修饰符;这会将文本的颜色更改为绿色。
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Turtle Rock")
.font(.title)
.foregroundColor(.green)
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}

您的代码始终真实反应到视图上。因此当使用检查器更改或移除修饰符时,Xcode会立即更新视图以匹配代码。
步骤五
然后我们在代码编辑区,按住command并单击Text来打开检查器,然后从弹出窗口中选择Inspect。单击Color弹出菜单,然后选择Inherited , 将文本颜色再次更改为黑色。

步骤六
请注意,Xcode会自动更新代码以反映您的更改,并删除foregroundColor(.green)修饰符。
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Turtle Rock")
.font(.title)
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}

第三节
使用堆栈组合视图
除了在上一节中创建的标题视图之外,还将添加文本视图,以包含有关地标的详细信息,例如公园的名称和所在的州。
创建SwiftUI视图时,可以在视图的body属性中描述其内容、布局和行为;但是,body属性只返回单个视图。您可以将多个视图组合并嵌入到Stack中,从而将视图水平、垂直或前后组合在一起。
在本节中,您将使用VStack(垂直堆栈)组合标题信息,使用HStack(水平堆栈)组合公园详情信息。

你可以使用Xcode的结构化编辑功能在容器视图中嵌入视图,也可以打开
Inspector或使用help进行其他更多有用的更改。
步骤一
按住command并单击Text视图,会看到初始值设定项的显示结构化编辑弹出窗口,然后选择Embed in VStack。

接下来,通过从
组件库中拖动Text视图,将Text视图添加到Stack中。
通过单击Xcode窗口右上角的加号按钮(+)打开组件库,然后将Text视图拖到Turtle Rock文本视图下面的位置。

步骤三
将Text视图的默认文本替换为“Joshua Tree National Park”。
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text("Turtle Rock")
.font(.title)
Text("Joshua Tree National Park")
}
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}

自定义位置文本的样式,以满足布局需求。
步骤四
将位置的font设置为subheadline 。
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text("Turtle Rock")
.font(.title)
Text("Joshua Tree National Park")
.font(.subheadline)
}
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}

步骤五
编辑VStack初始值设定项,以leading方式对齐视图。
默认情况下,VStack会将它们的内容按轴中心对齐,并提供上下文适当的间距。
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Turtle Rock")
.font(.title)
Text("Joshua Tree National Park")
.font(.subheadline)
}
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}

接下来,您将在位置文本的右侧添加另一个文本视图,该视图用于展示公园所在的州。
步骤六
在画布中,按住command并单击Joshua Tree National Park,然后选择Embed In HStack。

步骤七
在地点文本后添加新的文本视图,将默认文本更改为公园所在的州,然后将其font设置为subheadline 。
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Turtle Rock")
.font(.title)
HStack {
Text("Joshua Tree National Park")
.font(.subheadline)
Text("California")
.font(.subheadline)
}
}
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}

步骤八
若想使布局自动撑开并充满设备的宽,在
HStack中添加Spacer来分隔Joshua Tree National Park和California两个文本视图。
Spacer将撑开Stack,以使其包含的视图充满其父视图的所有空间,而不是仅由其内容定义其大小。
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Turtle Rock")
.font(.title)
HStack {
Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
.font(.subheadline)
}
}
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}

步骤九
最后,使用padding()修饰符方法给整个地标信息区域加一个边距。
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Turtle Rock")
.font(.title)
HStack {
Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
.font(.subheadline)
}
}
.padding()
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}

第四节
创建自定义图像视图
在名称和地点视图都设置好的情况下,接下来要做的是为地标添加一个图像。
对自定义的图像使用遮罩、边框和阴影,和原来的方法相比,将使用很少的代码。

首先将图片添加到项目的
Assets中。
步骤一
在项目Resources文件夹的中找到turtlerock.png,将其拖到Assets中。Xcode为图像创建一个新的图像集。

接下来,您将为您的自定义图像视图创建一个新的SwiftUI视图。
步骤二
选择File > New > File再次打开模板选择器。在User Interface中,单击选择SwiftUI View,然后单击Next。将文件命名为CircleImage.swift,然后单击Create。

现在您已准备好图像,以满足设计所需。
步骤三
使用Image(:)将Text视图替换为Turtle Rock的图像视图。
CircleImage.swift
import SwiftUI
struct CircleImage: View {
var body: some View {
Image("turtlerock")
}
}
struct CircleImage_Preview: PreviewProvider {
static var previews: some View {
CircleImage()
}
}

步骤四
添加Image的.clipShape(Circle())的修饰符,将图像剪裁为圆形。
Circle()是一个可以用作遮罩的形状,或通过给Circle()设置stroke或fill来绘制视图。
CircleImage.swift
import SwiftUI
struct CircleImage: View {
var body: some View {
Image("turtlerock")
.clipShape(Circle())
}
}
struct CircleImage_Preview: PreviewProvider {
static var previews: some View {
CircleImage()
}
}

步骤五
创建另一个带有灰色stroke的Circle(),使用.overlay()修饰符,将其覆盖添加到图像的边框中。
CircleImage.swift
import SwiftUI
struct CircleImage: View {
var body: some View {
Image("turtlerock")
.clipShape(Circle())
.overlay(
Circle().stroke(Color.gray, lineWidth: 4))
}
}
struct CircleImage_Preview: PreviewProvider {
static var previews: some View {
CircleImage()
}
}

步骤六
接下来,添加半径为10point的阴影。
CircleImage.swift
import SwiftUI
struct CircleImage: View {
var body: some View {
Image("turtlerock")
.clipShape(Circle())
.overlay(
Circle().stroke(Color.gray, lineWidth: 4))
.shadow(radius: 10)
}
}
struct CircleImage_Preview: PreviewProvider {
static var previews: some View {
CircleImage()
}
}

步骤七
将边框颜色修改为白色。
到此,我们就完成了图像视图。
CircleImage.swift
import SwiftUI
struct CircleImage: View {
var body: some View {
Image("turtlerock")
.clipShape(Circle())
.overlay(
Circle().stroke(Color.white, lineWidth: 4))
.shadow(radius: 10)
}
}
struct CircleImage_Preview: PreviewProvider {
static var previews: some View {
CircleImage()
}
}

第五节
UIKit和SwiftUI配合使用
现在可以创建地图视图了,您可以使用MapKit中的MKMappView类来绘制地图。
要在SwiftUI中使用UIView子类,可以将另一个视图包装在遵守UIViewRepresentable协议的SwiftUI视图中。SwiftUI包含了WatchKit和AppKit视图类似的协议。

现在开始,您将创建一个新的自定义视图,该视图可以显示
MKMapView。
选择File > New > File,选择iOS平台,选择SwiftUI View模板,然后单击Next。将新文件命名为MapView.swift,然后单击“Create”。

步骤二
添加import MapKit,并让MappView结构遵守UIViewRepresentable协议。
不要担心Xcode显示的警告;您将在接下来的几个步骤中修复它。
MapView.swift
import SwiftUI
import MapKit
struct MapView: UIViewRepresentable {
var body: some View {
Text("Hello World")
}
}
struct MapView_Preview: PreviewProvider {
static var previews: some View {
MapView()
}
}
UIViewRepresentable协议有两个需要实现的方法:使用UIView(context:)方法创建MKMapView,使用updateUIView(u:context:)方法来配置视图并响应视图的任意变化。
步骤三
用makeUIView(context:)方法替换body属性。
MapView.swift
import SwiftUI
import MapKit
struct MapView: UIViewRepresentable {
func makeUIView(context: Context) -> MKMapView {
MKMapView(frame: .zero)
}
}
struct MapView_Preview: PreviewProvider {
static var previews: some View {
MapView()
}
}
步骤四
创建一个updateUIView(u:context:)方法,将地图视图的区域设置为正确的坐标,以便将地图居中放置在Turtle Rock上。
MapView.swift
import SwiftUI
import MapKit
struct MapView: UIViewRepresentable {
func makeUIView(context: Context) -> MKMapView {
MKMapView(frame: .zero)
}
func updateUIView(_ view: MKMapView, context: Context) {
let coordinate = CLLocationCoordinate2D(
latitude: 34.011286, longitude: -116.166868)
let span = MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)
let region = MKCoordinateRegion(center: coordinate, span: span)
view.setRegion(region, animated: true)
}
}
struct MapView_Preview: PreviewProvider {
static var previews: some View {
MapView()
}
}
当预览处于静态模式时,它们仅完全呈现
SwiftUI视图。因为MKMapView是一个UIView子类,所以您需要切换到实时预览来查看地图。
步骤五
单击Live Preview按钮将预览切换到实时预览模式。您可能需要单击预览上方的Try Again或Resume按钮。
再过一会儿,你会看到一张Joshua Tree National Park的地图,那里是Turtle Rock的故乡。

第六节
组成详情视图
现在您已经拥有了所需的所有组件——名称和位置、圆形图像以及地图。
现在使用目前的工具,组合自定义视图,创建地标详情视图以达到最终设计吧。

步骤一
在项目导航器中,选择ContentView.swift文件。
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Turtle Rock")
.font(.title)
HStack {
Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
.font(.subheadline)
}
}
.padding()
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}

步骤二
将刚才的三个文本的VStack嵌入到另一个VStack中。
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
VStack(alignment: .leading) {
Text("Turtle Rock")
.font(.title)
HStack(alignment: .top) {
Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
.font(.subheadline)
}
}
.padding()
}
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}

步骤三
将自定义
MapView添加到Stack顶部。使用frame(width:height:)设置MapView的大小。
仅指定height(高度)参数时,视图会自动调整其内容的宽度。在这种情况下,MapView将展开以填充可用空间。
步骤四
单击Live Preview按钮,以在预览中查看渲染的地图。
您可以在显示实时预览时继续编辑视图。
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
MapView()
.frame(height: 300)
VStack(alignment: .leading) {
Text("Turtle Rock")
.font(.title)
HStack(alignment: .top) {
Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
.font(.subheadline)
}
}
.padding()
}
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}

步骤五
将CircleImage视图添加到Stack中。
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
MapView()
.frame(height: 300)
CircleImage()
VStack(alignment: .leading) {
Text("Turtle Rock")
.font(.title)
HStack(alignment: .top) {
Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
.font(.subheadline)
}
}
.padding()
}
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}

步骤六
若要将CircleImage显示在MapView视图的上方,请使图像垂直偏移-130个点,并从视图底部填充-130个点。
这些调整通过向上移动图像为文本腾出空间。
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
MapView()
.frame(height: 300)
CircleImage()
.offset(y: -130)
.padding(.bottom, -130)
VStack(alignment: .leading) {
Text("Turtle Rock")
.font(.title)
HStack(alignment: .top) {
Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
.font(.subheadline)
}
}
.padding()
}
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}

步骤七
在VStack中底部添加一个Spacer(),将内容推到屏幕的顶部。
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
MapView()
.frame(height: 300)
CircleImage()
.offset(y: -130)
.padding(.bottom, -130)
VStack(alignment: .leading) {
Text("Turtle Rock")
.font(.title)
HStack(alignment: .top) {
Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
.font(.subheadline)
}
}
.padding()
Spacer()
}
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}

步骤八
最后,要允许地图内容扩展到屏幕的上边缘,请将edgesIgnoringSafeArea(.top)修饰符添加到地图视图中。
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
MapView()
.edgesIgnoringSafeArea(.top)
.frame(height: 300)
CircleImage()
.offset(y: -130)
.padding(.bottom, -130)
VStack(alignment: .leading) {
Text("Turtle Rock")
.font(.title)
HStack(alignment: .top) {
Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
.font(.subheadline)
}
}
.padding()
Spacer()
}
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}
