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()
}
}