由于 API 變動汞窗,此文章部分內(nèi)容已失效败明,最新完整中文教程及代碼請查看 https://github.com/WillieWangWei/SwiftUI-Tutorials
微信技術(shù)群
SwiftUI
代表未來構(gòu)建 App 的方向谷醉,歡迎加群一起交流技術(shù)哼绑,解決問題风秤。
加群現(xiàn)在需要申請了晋涣,可以先加我微信减江,備注 "SwiftUI"染突,我會拉你進(jìn)群。
創(chuàng)建和組合 View
此部分將指引你構(gòu)建一個發(fā)現(xiàn)和分享您喜愛地方的 iOS app ——
Landmarks
辈灼。首先我們來構(gòu)建顯示地標(biāo)詳細(xì)信息的 view份企。
Landmarks
使用stacks
將image
、text
等組件進(jìn)行組合和分層巡莹,以此來給 view 布局司志。如果想給視圖添加地圖,我們需要引入標(biāo)準(zhǔn)MapKit
組件降宅。在我們調(diào)整設(shè)計時骂远,Xcode 可以作出實時反饋,以便我們看到這些調(diào)整是如何轉(zhuǎn)換為代碼的腰根。下載項目文件并按照以下步驟操作激才。
- 預(yù)計完成時間:40 分鐘
- 初始項目文件:下載
1. 創(chuàng)建一個新項目并且瀏覽 Canvas
用 SwiftUI
的 app 模板來創(chuàng)建一個新的 Xcode 項目,并且瀏覽一下這個 canvas。
1.1 打開 Xcode 瘸恼,在 Xcode 的啟動窗口中單擊 Create a new Xcode project
劣挫,或選擇 File
> New
> Project
。
1.2 選擇 iOS
平臺东帅, Single View App
模板压固,然后單擊 Next
。
1.3 輸入 Landmarks
作為 Product Name
靠闭,勾選 Use SwiftUI
復(fù)選框帐我,然后單擊 Next
。選擇一個位置保存此項目愧膀。
1.4 在 Project navigator
中拦键,選中 ContentView.swift
。
默認(rèn)情況下檩淋, SwiftUI
view 文件聲明了兩個結(jié)構(gòu)體矿咕。第一個結(jié)構(gòu)體遵循 View
協(xié)議,描述 view 的內(nèi)容和布局狼钮。第二個結(jié)構(gòu)體聲明該 view 的預(yù)覽。
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello World")
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}
1.5 在 canvas
中捡絮,單擊 Resume
來顯示預(yù)覽熬芜。
Tip:如果沒有
canvas
,選擇Editor
>Editor and Canvas
來顯示福稳。
1.6 在 body
屬性中涎拉,將 Hello World
更改為自己的問候語。更改代碼時的圆,預(yù)覽便會實時更新鼓拧。
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello SwiftUI!")
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}
2. 自定義 Text View
為了自定義 view 的顯示,我們可以自己更改代碼越妈,或者使用 inspector
來幫助我們編寫代碼季俩。
在構(gòu)建 Landmarks
的過程中,我們可以使用任何編輯器來工作:編寫源碼梅掠、修改 canvas
酌住、或者通過 inspectors
,無論使用哪種工具阎抒,代碼都會保持更新酪我。
接下來,我們使用 inspector
來自定義 text view
且叁。
2.1 在預(yù)覽中都哭,按住 Command
并單擊問候語來顯示編輯窗口,然后選擇 Inspect
。
編輯窗口顯示了可以修改的不同屬性欺矫,具體取決于其 view 類型纱新。
2.2 用 inspector
將文本改為 Turtle Rock
,這是在 app 中顯示的第一個地標(biāo)的名字汇陆。
2.3 將 Font
修改為 Title
怒炸。
這個修改會讓文本使用系統(tǒng)字體,之后它就能正確顯示用戶的偏好字體大小和設(shè)置毡代。
Edit the code by hand to add the .color(.green) modifier; this changes the text’s color to green.
To customize a SwiftUI view, you call methods called modifiers. Modifiers wrap a view to change its display or other properties. Each modifier returns a new view, so it’s common to chain multiple modifiers, stacked vertically.
2.4 在代碼中添加 .color(.green)
阅羹,將文本的顏色更改為綠色。
如果想自定義 SwiftUI
的 view教寂,我們可以調(diào)用一類叫做 modifiers
的方法捏鱼。這類方法通過包裝一個 view 來改變它的顯示或者其他屬性。每個 modifiers
方法會返回一個新的 view酪耕,因此我們可以鏈?zhǔn)秸{(diào)用多個 modifiers
方法导梆。
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Turtle Rock")
.font(.title)
.color(.green)
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}
view 的真實來源是其實是代碼,當(dāng)我們使用 inspector
修改或刪除 modifiers
時迂烁,Xcode 會立即更新我們的代碼看尼。
2.5 這次我們在代碼編輯區(qū)按住 Command
,單擊 Text
的聲明來打開 inspector
盟步,然后選擇 Inspect
藏斩。單擊顏色菜單并且選擇 Inherited
,這樣文字又變回了黑色却盘。
2.6 注意狰域,Xcode 會自動針對修改來更新代碼,例如刪除了 .color(.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()
}
}
3. 用 Stacks 組合 View
在上一節(jié)創(chuàng)建標(biāo)題 view 后兆览,我們來添加 text view,它用來顯示地標(biāo)的詳細(xì)信息塞关,比如公園的名稱和所在的州抬探。
在創(chuàng)建 SwiftUI
view 時,我們可以在 view 的 body
屬性中描述其內(nèi)容帆赢、布局和行為驶睦。由于 body
屬性僅返回單個 view,所以我們可以使用 Stacks
來組合和嵌入多個 view匿醒,讓它們以水平场航、垂直或從后到前的順序組合在一起。
在本節(jié)中廉羔,我們使用水平的 stack
來顯示公園的詳細(xì)信息溉痢,再用垂直的 stack
將標(biāo)題放在詳細(xì)信息的上面僻造。
我們可以使用 Xcode 的編輯功能將 view 嵌入到一個容器里,也可以使用 inspector
或者 help
找到更多幫助孩饼。
3.1 按住 Command
并單擊 text view 的初始化方法髓削,在編輯窗口中選擇 Embed in VStack
。
接下來镀娶,我們從 Library
中拖一個 Text view
添加到 stack
中立膛。
3.2 單擊 Xco??de 右上角的加號按鈕 (+)
打開 Library
,然后拖一個 Text view
梯码,放在代碼中 Turtle Rock
的后面宝泵。
3.3 將 Placeholder
改成 Joshua Tree National Park
。
ContentView.swift
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()
}
}
調(diào)整地點 view 以滿足布局需求轩娶。
3.4 將地點 view 的 font
設(shè)置成 .subheadline
儿奶。
ContentView.swift
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()
}
}
3.5 編輯 VStack
的初始化方法,將 view 以 leading
方式對齊鳄抒。
默認(rèn)情況下闯捎, stacks
會將內(nèi)容沿其軸居中,并設(shè)置適合上下文的間距许溅。
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Turtle Rock")
.font(.title)
Text("Joshua Tree National Park")
.font(.subheadline)3
}
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}
接下來瓤鼻,我們在地點的右側(cè)添加另一個 text view 來顯示公園所在的州。
3.6 在 canvas
中按住 Command
贤重,單擊 Joshua Tree National Park
娱仔,然后選擇 Embed in HStack
。
3.7 在地點后新加一個 text view游桩,將 Placeholder
修改成 California
,然后將 font
設(shè)置成 .subheadline
耐朴。
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)
Text("California")
.font(.subheadline)
}
}
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}
3.8 在水平 stack
中添加一個 Spacer
來分割及固定 Joshua Tree National Park
和 California
借卧,這樣它們就會共享整個屏幕寬度。
spacer
能展開它包含的 view 筛峭,使它們共用其父 view 的所有空間铐刘,而不是僅通過其內(nèi)容定義其大小。
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)
}
}
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}
3.9 最后影晓,用 .padding()
這個修飾方法給地標(biāo)的名稱和信息留出一些空間镰吵。
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()
}
}
4. 自定義 Image View
搞定名稱和位置 view 后,我們來給地標(biāo)添加圖片挂签。
這不需要添加很多代碼疤祭,只需要創(chuàng)建一個自定義 view,然后給圖片加上遮罩饵婆、邊框和陰影即可勺馆。
首先將圖片添加到項目的 asset catalog
中。
4.1 在項目的 Resources
文件夾中找到 turtlerock.png
,將它拖到 asset catalog
的編輯器中草穆。 Xcode 會給圖片創(chuàng)建一個 image set
灌灾。
接下來,創(chuàng)建一個新的 SwiftUI
view 來自定義 image view悲柱。
4.2 選擇 File
> New
> File
打開模板選擇器锋喜。在 User Interface
中,選中 SwiftUI View
豌鸡,然后單擊 Next
嘿般。將文件命名為 CircleImage.swift
,然后單擊 Create
直颅。
現(xiàn)在準(zhǔn)備工作已完成博个。
4.3 使用 Image(_:)
初始化方法將 text view 替換為 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()
}
}
4.4 調(diào)用 .clipShape(Circle())
功偿,將圖像裁剪成圓形盆佣。
Circle
可以當(dāng)做一個蒙版的形狀,也可以通過 stroke
或 fill
形成 view械荷。
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()
}
}
4.5 創(chuàng)建另一個 gray 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()
}
}
4.6 接來下痹兜,添加一個半徑為 10 point 的陰影。
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()
}
}
4.7 將邊框的顏色改為 white
颤诀,完成 image view字旭。
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()
}
}
5. 同時使用 UIKit 和 SwiftUI
至此,我們已準(zhǔn)備好創(chuàng)建 map view 了崖叫,接下來使用 MapKit
中的 MKMapView
類來渲染地圖遗淳。
在 SwiftUI
中使用 UIView
子類,需要將其他 view 包裝在遵循 UIViewRepresentable
協(xié)議的 SwiftUI
view 中心傀。 SwiftUI
包含了和 WatchKit
屈暗、 AppKit
view 類似的協(xié)議。
首先脂男,我們創(chuàng)建一個可以呈現(xiàn) MKMapView
的自定義 view养叛。
5.1 選擇 File
> New
> File
,選擇 iOS
平臺宰翅,選擇 SwiftUI View
模板弃甥,然后單擊 Next
。將新文件命名為 MapView.swift
汁讼,然后單擊 Create
潘飘。
5.2 給 MapKit
添加 import
語句肮之,聲明 MapView
類型遵循 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
協(xié)議需要實現(xiàn)兩個方法: makeUIView(context:)
用來創(chuàng)建一個 MKMapView
, updateUIView(_:context:)
用來配置 view 并響應(yīng)修改艰毒。
5.3 用 makeUIView(context:)
方法替換 body
屬性筐高,該方法創(chuàng)建并返回一個空的 MKMapView
。
MapView.swift
import SwiftUI
import MapKit
struct MapView: UIViewRepresentable {
typealias UIViewType = MKMapView
func makeUIView(context: UIViewRepresentableContext<MapView>) -> MKMapView {
return MKMapView(frame: .zero)
}
}
struct MapView_Preview: PreviewProvider {
static var previews: some View {
MapView()
}
}
5.4 實現(xiàn) updateUIView(_:context:)
方法丑瞧,給 map view 設(shè)置坐標(biāo)柑土,使其在 Turtle Rock
上居中。
MapView.swift
import SwiftUI
import MapKit
struct MapView : UIViewRepresentable {
typealias UIViewType = MKMapView
func makeUIView(context: UIViewRepresentableContext<MapView>) -> MKMapView {
return MKMapView(frame: .zero)
}
func updateUIView(_ uiView: MKMapView, context: UIViewRepresentableContext<MapView>) {
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)
uiView.setRegion(region, animated: true)
}
}
struct MapView_Preview: PreviewProvider {
static var previews: some View {
MapView()
}
}
當(dāng)預(yù)覽處于 static mode
時僅顯示 SwiftUI
view 绊汹。因為 MKMapView
是一個 UIView
的子類稽屏,所以需要切換到實時模式才能看到地圖。
5.5 單擊 Live Preview
可將預(yù)覽切換為實時模式西乖,有時也會用到 Try Again
或 Resume
按鈕狐榔。
片刻之后,你會看到 Joshua Tree National Park
的地圖获雕,這是 Turtle Rock
的故鄉(xiāng)薄腻。
6. 編寫詳情 View
現(xiàn)在我們完成了所需的所有組件:名稱、地點届案、圓形圖片和地圖庵楷。
繼續(xù)使用目前的工具,將這些組件組合起來變成符合最終設(shè)計的詳情 view楣颠。
6.1 在項目導(dǎo)航中尽纽,選中 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()
}
}
6.2 把之前的的 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()
}
}
6.3 將自定義的 MapView
添加到 stack
頂部弄贿,使用 frame(width:height:)
方法來設(shè)置 MapView
的大小。
如果僅指定了 height
參數(shù)睁冬,view 會自動調(diào)整其內(nèi)容的寬度。此節(jié)中看疙, MapView
會展開并填充所有可用空間豆拨。
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()
}
}
6.4 單擊 Live Preview
按鈕,在組合 view 中查看渲染的地圖能庆。
在此過程中施禾,我們可以繼續(xù)編輯 view。
6.5 將 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()
}
}
6.6 為了將 image view
布局在 map view
的頂部弥搞,我們需要給圖片設(shè)置 -130 points
的偏移量邮绿,并從底部填充 -130 points
。
圖片向上移動后攀例,就為文本騰出了空間船逮。
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()
}
}
6.7 在外部 VStack
的底部添加一個 spacer
,將內(nèi)容推到屏幕頂端粤铭。
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()
}
}
6.8 最后挖胃,為了將地圖內(nèi)容擴(kuò)展到屏幕的上邊緣,需要將 edgesIgnoringSafeArea(.top)
添加到 map view 中梆惯。
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()
}
}