因爲 API 變更,此文章部份內容已失效,最新完整中文教程及代碼請查看 github.com/WillieWangW…git
SwiftUI
表明將來構建 App 的方向,歡迎加羣一塊兒交流技術,解決問題。github
此部分將指引你構建一個發現和分享您喜好地方的 iOS app ——
Landmarks
。首先咱們來構建顯示地標詳細信息的 view。canvas
Landmarks
使用stacks
將image
、text
等組件進行組合和分層,以此來給 view 佈局。若是想給視圖添加地圖,咱們須要引入標準MapKit
組件。在咱們調整設計時,Xcode 能夠做出實時反饋,以便咱們看到這些調整是如何轉換爲代碼的。swift下載項目文件並按照如下步驟操做。bash
- 預計完成時間:40 分鐘
- 初始項目文件:下載
用 SwiftUI
的 app 模板來建立一個新的 Xcode 項目,而且瀏覽一下這個 canvas。微信
1.1 打開 Xcode ,在 Xcode 的啓動窗口中單擊 Create a new Xcode project
,或選擇 File
> New
> Project
。app
1.2 選擇 iOS
平臺, Single View App
模板,而後單擊 Next
。編輯器
1.3 輸入 Landmarks
做爲 Product Name
,勾選 Use SwiftUI
複選框,而後單擊 Next
。選擇一個位置保存此項目。ide
1.4 在 Project navigator
中,選中 ContentView.swift
。工具
默認狀況下, SwiftUI
view 文件聲明瞭兩個結構體。第一個結構體遵循 View
協議,描述 view 的內容和佈局。第二個結構體聲明該 view 的預覽。
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
來顯示預覽。
Tip:若是沒有
canvas
,選擇Editor
>Editor and Canvas
來顯示。
1.6 在 body
屬性中,將 Hello World
更改成本身的問候語。更改代碼時,預覽便會實時更新。
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello SwiftUI!")
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}
複製代碼
爲了自定義 view 的顯示,咱們能夠本身更改代碼,或者使用 inspector
來幫助咱們編寫代碼。
在構建 Landmarks
的過程當中,咱們可使用任何編輯器來工做:編寫源碼、修改 canvas
、或者經過 inspectors
,不管使用哪一種工具,代碼都會保持更新。
接下來,咱們使用 inspector
來自定義 text view
。
2.1 在預覽中,按住 Command
並單擊問候語來顯示編輯窗口,而後選擇 Inspect
。
編輯窗口顯示了能夠修改的不一樣屬性,具體取決於其 view 類型。
2.2 用 inspector
將文本改成 Turtle Rock
,這是在 app 中顯示的第一個地標的名字。
2.3 將 Font
修改成 Title
。
這個修改會讓文本使用系統字體,以後它就能正確顯示用戶的偏好字體大小和設置。
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,咱們能夠調用一類叫作 modifiers
的方法。這類方法經過包裝一個 view 來改變它的顯示或者其餘屬性。每一個 modifiers
方法會返回一個新的 view,所以咱們能夠鏈式調用多個 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 的真實來源是實際上是代碼,當咱們使用 inspector
修改或刪除 modifiers
時,Xcode 會當即更新咱們的代碼。
2.5 此次咱們在代碼編輯區按住 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()
}
}
複製代碼
在上一節建立標題 view 後,咱們來添加 text view,它用來顯示地標的詳細信息,好比公園的名稱和所在的州。
在建立 SwiftUI
view 時,咱們能夠在 view 的 body
屬性中描述其內容、佈局和行爲。因爲 body
屬性僅返回單個 view,因此咱們可使用 Stacks
來組合和嵌入多個 view,讓它們以水平、垂直或從後到前的順序組合在一塊兒。
在本節中,咱們使用水平的 stack
來顯示公園的詳細信息,再用垂直的 stack
將標題放在詳細信息的上面。
咱們可使用 Xcode 的編輯功能將 view 嵌入到一個容器裏,也可使用 inspector
或者 help
找到更多幫助。
3.1 按住 Command
並單擊 text view 的初始化方法,在編輯窗口中選擇 Embed in VStack
。
接下來,咱們從 Library
中拖一個 Text view
添加到 stack
中。
3.2 單擊 Xcode 右上角的加號按鈕 (+)
打開 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()
}
}
複製代碼
調整地點 view 以知足佈局需求。
3.4 將地點 view 的 font
設置成 .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
方式對齊。
默認狀況下, stacks
會將內容沿其軸居中,並設置適合上下文的間距。
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()
}
}
複製代碼
接下來,咱們在地點的右側添加另外一個 text view 來顯示公園所在的州。
3.6 在 canvas
中按住 Command
,單擊 Joshua Tree National Park
,而後選擇 Embed in HStack
。
3.7 在地點後新加一個 text view,將 Placeholder
修改爲 California
,而後將 font
設置成 .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 的全部空間,而不是僅經過其內容定義其大小。
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()
這個修飾方法給地標的名稱和信息留出一些空間。
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()
}
}
複製代碼
搞定名稱和位置 view 後,咱們來給地標添加圖片。
這不須要添加不少代碼,只須要建立一個自定義 view,而後給圖片加上遮罩、邊框和陰影便可。
首先將圖片添加到項目的 asset catalog
中。
4.1 在項目的 Resources
文件夾中找到 turtlerock.png
,將它拖到 asset catalog
的編輯器中。 Xcode 會給圖片建立一個 image set
。
接下來,建立一個新的 SwiftUI
view 來自定義 image view。
4.2 選擇 File
> New
> File
打開模板選擇器。在 User Interface
中,選中 SwiftUI View
,而後單擊 Next
。將文件命名爲 CircleImage.swift
,而後單擊 Create
。
如今準備工做已完成。
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 調用 .clipShape(Circle())
,將圖像裁剪成圓形。
Circle
能夠當作一個蒙版的形狀,也能夠經過 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 建立另外一個 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()
}
}
複製代碼
至此,咱們已準備好建立 map view 了,接下來使用 MapKit
中的 MKMapView
類來渲染地圖。
在 SwiftUI
中使用 UIView
子類,須要將其餘 view 包裝在遵循 UIViewRepresentable
協議的 SwiftUI
view 中。 SwiftUI
包含了和 WatchKit
、 AppKit
view 相似的協議。
首先,咱們建立一個能夠呈現 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
協議須要實現兩個方法: makeUIView(context:)
用來建立一個 MKMapView
, updateUIView(_:context:)
用來配置 view 並響應修改。
5.3 用 makeUIView(context:)
方法替換 body
屬性,該方法建立並返回一個空的 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 實現 updateUIView(_:context:)
方法,給 map view 設置座標,使其在 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()
}
}
複製代碼
當預覽處於 static mode
時僅顯示 SwiftUI
view 。由於 MKMapView
是一個 UIView
的子類,因此須要切換到實時模式才能看到地圖。
5.5 單擊 Live Preview
可將預覽切換爲實時模式,有時也會用到 Try Again
或 Resume
按鈕。
片刻以後,你會看到 Joshua Tree National Park
的地圖,這是 Turtle Rock
的故鄉。
如今咱們完成了所需的全部組件:名稱、地點、圓形圖片和地圖。
繼續使用目前的工具,將這些組件組合起來變成符合最終設計的詳情 view。
6.1 在項目導航中,選中 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:)
方法來設置 MapView
的大小。
若是僅指定了 height
參數,view 會自動調整其內容的寬度。此節中, 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 中查看渲染的地圖。
在此過程當中,咱們能夠繼續編輯 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
的頂部,咱們須要給圖片設置 -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
,將內容推到屏幕頂端。
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 最後,爲了將地圖內容擴展到屏幕的上邊緣,須要將 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()
}
}
複製代碼