创建和组合视图

这个教程指导你构建一个名为Landmarks(地标)的应用。这个应用的功能是可以发现并分享你喜欢的地标。首先从创建地标详情页开始。

Landmarks使用栈来按层组合图片、文本等视图元素,从而布局页面。在视图中添加地图,需要引入MapKit组件,在你布局页面的过程中, Xcode可以提供实时的反馈,让你所做的改动立即转化成对应的代码实现。


第一节 创建新项目并体验画布

创建SwiftUI项目工程,体验画布、预览模式和SwiftUI模板代码

要想在Xcode中预览画布中的视图或者与画布中的视图进行交互,要求你的Mac系统版本号不低于macOS Catalina 10.15

create new project

步骤1 打开Xcode,在启动页面点击创建新工程或者在菜单中选择文件->新建->项目

create new project xcode

create new project xcode menu

步骤2 在项目模板选择器中,选择iOS作为项目平台,选项单视图应用(Single View App)作为项目模板,并点击下一步(Next)

create new project app template

步骤3 输入Landmarks作为项目名称,选择SwiftUI作为用户界面的创建方式,并点击下一步(Next),在磁盘目录下选择一个位置用来存放新创建的工程项目

create new project info

步骤4 工程创建好并打开后,在文件导航器中,选择ContentView.swift文件,可以浏览一下SwiftUI视图的组成结构。默认情况下,SwiftUI的视图文件包含两个结构体(Struct) 第一个结构体遵循View协议,描述视图的内容和布局。第二个结构体声明为第一个视图的预览视图。

步骤5 在**画布(Canvas)上,点击恢复(Resume)**按钮可以显示预览视图,也可以使用快捷键Command+Option+P

如果工程中没有出现画布(Canvas),可以选择菜单:编辑器(Editor) -> 编辑器和画布(Editor and Canvas) 打开画布进行预览

create new project completed

步骤6body属性内部,修改文字Hello World为其它的不同的文字,当你在改变代码的同时,预览视图也会实时的更新对应的内容变化

creating and combining views

第二节 定制文本视图(Text View)

可能通过修改代码来改变一个视图的显示样式,也可以通过检查器获取视图可修改属性,然后再写对应的代码改变样式。在创建应用的过程中,可以同时使用源码编辑器、画布或者检查器,无论当前使用的是哪一个工具编辑视图,代码会保持和这些编辑器展示的样式一致

customize text view

下面我们使用检查器来定制视图的显示样式

步骤1 在预览视图中,按下Command键的同时点击控件,会弹出一个编辑弹层,然后选择检查器(Inspect), 编辑弹层显示所有可以定制的视图属性,选中的控件不同,可以定制的属性集合也不相同

swift preview inspectror

步骤2 使用检查器把文字更改为Turtle Rock,也就是在应用中显示的第一个地标的名称

swiftui preivew inspector change text

步骤3 改变字体修改器为Title,使用系统字体修饰文字,可以自动按照用户在设备中设置的字体偏好大小进行调整。定制SwiftUI视图所调用的方法被称为视图修改器(Modifiers),修改器在原视图的基础上修改部分显示样式和属性,返回一个新的视图,这样就可以让多个修改器串连进行,形成水平方向的链式调用,或者垂直方向的堆叠调用

swiftui preview inspector change font

步骤4 手动在代码中添加foregroundColor(.green) 属性修改器,就会把文字的颜色调整为绿色。代码是决定视图样式的根本,当我们使用检查器来改变或移除一个属性修改器时,Xcode也会在代码编辑器中同步改变或移除对应的修改器代码

swiftui code change foreground color

步骤5 在代码编辑器中,按下Command的同时点击Text单词也可以属性弹窗,从中选择检查器后,再点击Color弹出菜单,选择继承(Inherited),让文字的颜色恢复成原来的黑色

swiftui code inspector resume font

步骤6 当我们移除 foregroundColor(.green) 时,Xcode会自动更新你的代码来反映视图的实际显示状况

swiftui xcode resume

第三节 使用栈来组合视图

上一节创建了标题视图,接下来要添加一些文本视图来描述地标所在州及所在公园的名称等其它详细信息

swiftui layout stack

创建SwiftUI视图就是在body属性中描述视图的内容、布局及行为,但body属性只返回单个视图,这时组合多个视图时可以把它们放入一个栈中,通过水平、垂直、前后嵌套多个视图完成视图组合,做为一个整体在body属性中返回

这一节中,使用一个垂直栈,把标题放在包含公园详情的水平栈的上方,在水平栈中,布局公园详情相关的内容

可以使用Xcode提供的结构化布局来把视图嵌套在容器视图中

步骤1 按下Command键的同时,点击Text视图的初始化代码打开结构化编辑弹窗,然后选择把控件嵌套在垂直栈中(Embed in VStack),在栈中添加Text View控件可以从组件中直接拖进栈中完成

swiftui view embed in vertical stack

步骤2 点击Xcode右上角的**+**号,托动一个Text控件到指定位置,代码立即就会在编辑器中补全

步骤3Text视图的占位文本修改为Joshua Tree Nation Park,视图会自动调整位置布局

步骤4 设置位置控件的字体为子标题样式

swiftui inspector add text view

步骤5 设置VStack初始化参数为左对齐内部的子视图。默认情况下,栈会把内部视图在自己的主轴上居中对齐,并自动计算各子视图的间距。下一步要添加一个Text控制用来描述公园的状态,它水平排列在位置信息的右边。

swiftui vstack leadng alignment

步骤6 在画布内,command按下的同时点击位置视图,在弹出的菜单中选择嵌入到水平栈中(Embed in HStack)

步骤7 在位置控件的后面加一个公园状态的Text视图,并把占位文字改为California,字体设置为子标题样式

步骤8 为了水平布局使用整个屏幕宽度,在位置控件和公园状态控件中间添加一个Spacer控件,用来填充两个控件中间的空白部分,并把两个控件分别顶向屏幕的两侧。Spacer是一个可以伸缩的空白控件,他负责占用其它控件布局完成后剩下的所有空间。

步骤9 使用padding()修改器给地标信息内容视图整体加内边距

swiftui embed in hstack

第四节 创建自定义图像视图(Image)

有了地标名称、地标位置及状态视图,下一步再添加一个地标图片视图。这个图片视图将自定义遮罩(mask)、边框(border)和阴影(shadow)

从控件加中拖一个Image到画布,或直接写代码到代码编辑器中

步骤1 在项目资源文件中找到turtlerock.png图片,把它拖入资源编辑器(asset catalog editor)中,Xcode会创建一个新的图片集来存放这个图片,然后创建一个SwiftUI视图

swiftui assets catalog editor

步骤2 选择文件->新建->文件,打开模板选择器。在用户界面(User Interface)板块下,选择SwiftUI View并点击下一步,命名为CircleImage.swift,并点击创建(Create)。现在你已经准备好插入图片并修改布局来满足设计目标

swiftui create swiftui file

swiftui create circle image

步骤3Image替换Text,并使用turtlerock图片初始化Image视图

步骤4 添加clipShape(Circle())修改器到Image,给图片添加圆形剪切效果。Circle是一个形状,它可以被用作遮罩、也可以是圆圈,还可以是圆形填充视图。

步骤5 创建另一个灰色的圆圈并把它作为一个浮层添加到图片上,相当于给图片加了一个灰色边框

步骤6 给视图添加半径为10的阴影

swiftui turtlerock overlay

步骤7 把圆形边框的颜色改成白色,就完成了自定义图片视图的创建。

swiftui circle image completed

第五节 UIKit视图与SwiftUI视图混合使用

现在要创建一个地图视图,可以使用MapKit中的MKMapView视图类来渲染地图。要在SwiftUI中使用UIView及其子类,需要把这些UIView包裹在一个遵循UIViewRepresentable协议的SwiftUI视图中,SwiftUI中也包含适配WatchKitAppKit的类似的协议。

swiftui uikit swiftui combine

首先需要创建一个自定义视图用来容纳和显示MKMapView

步骤1 选择文件->新建->文件,选择iOS平台,选择SwiftUI View模板,并点击下一步(Next),命名文件为MapView.swift,并点击创建(Create)

步骤2 代码中导入MapKit引用,声明MapView遵循UIViewRepresentable协议。UIViewRepresentable协议要求实现两个方法UIView(context:)updateUIView(_:context:),第一个方法用来创建MKMapView,第二个方法用来配置视图响应状态变化

步骤3 替换body,用makeUIView(context:)方法来代替,创建并返回一个空的MKMapView

步骤4 创建方法updateUIView(_:context:),在方法内部设置地图视图的坐标为Turle Rock的中心。在静态模式下预览时,只会渲染swiftUI视图的部分,因为MKMapViewUIView的子类,所以需要切换到实时预览模式下才能看到地图被完全渲染出来

swiftui mapview mkmapview wrapper

步骤5 点击Live Preview(实时预览)按钮,可能需要点击Try AgainResume按钮来激活预览模式的切换。切换到实时预览模式下不久就可以看到指定地标所在的地图位置了

swiftui mkmapview live preview

第六节 组合地标详情页

前面我们创建了个地标详情页所需要的各种子视图元素:名称、地点、圆形图片以及位置地图,现在可以把这些视图元素组合在一起形成地标详情页的整个视图

swiftui combine view begin

  1. 在项目工程浏览器中选择ContentView.swift文件

  2. body属性中嵌入一个VStack视图,它内部包含另一个VStack视图,内部的VStack视图又包含三个Text视图

  3. 在外层VStack的顶部添加自定义的地图视图MapView,并使用frame(width:height:)设置视图大小。当只指定高度时,宽度会自动计算为父视图的宽度,在这里就是屏幕宽度

  4. 点击Live Preview按钮进入实时预览模式,查看地图渲染情况。在实时预览模式下可以编辑视图,最新的改动也可以实时的刷新出来。

  5. MapView后面再添加一个CircleImage视图

  6. 为了让图片视图叠放在地图视图的上面,可以设置图片视图的垂直偏移量为-130,图片视图的底部内边距也为-130,这个效果就是把图片垂直上移了130,同时和下面的文字区域留出了130的空白分隔区

  7. 在外层VStack内部的最下面加上Spacer,可以让上面的视图内容顶到屏幕的上边

  8. 为了让地图的视图内容显示在状态栏的下方,可以给MapView添加edgesIgnoringSafeArea(.top)修改器,这可以让它在布局时忽略顶部的安全区域边距

swiftui combine view completed

检查是否理解

问题1 在声明自定义SwiftUI视图时,视图布局要声明的在哪里?

  • 在视图初始化器中
  • body属性中
  • layoutSubviews方法中

View协议中要求实现body属性,每一个SwiftUI视图都遵循View协议

问题2 代码布局的视图是以下哪个?

swiftui combine view problem2

  • swiftui combine view problem2-1
  • swiftui combine view problem2-2
  • swiftui combine view problem2-3

问题3 下面哪种方法是从body属性中返回三个视图的正确方法?

  • swiftui combine view problem3-1
  • swiftui combine view problem3-2
  • swiftui combine view problem3-3

问题4 配置视图时,下面哪种是正确使用修改器的方式?

  • swiftui combine view problem4-1
  • swiftui combine view problem4-2
  • swiftui combine view problem4-3

修改器每次都是返回一个新的对象,所以多个修改器可以通过链式调用