[譯] SwiftUI 官方教程 (二)

因爲 API 變更,此文章部份內容已失效,最新完整中文教程及代碼請查看 github.com/WillieWangW…git

微信技術羣

SwiftUI 表明將來構建 App 的方向,歡迎加羣一塊兒交流技術,解決問題。github

建立和組合 View

此部分將指引你構建一個發現和分享您喜好地方的 iOS app —— Landmarks 。首先咱們來構建顯示地標詳細信息的 view。canvas

Landmarks 使用 stacksimagetext 等組件進行組合和分層,以此來給 view 佈局。若是想給視圖添加地圖,咱們須要引入標準 MapKit 組件。在咱們調整設計時,Xcode 能夠做出實時反饋,以便咱們看到這些調整是如何轉換爲代碼的。swift

下載項目文件並按照如下步驟操做。bash

  • 預計完成時間:40 分鐘
  • 初始項目文件:下載

1. 建立一個新項目而且瀏覽 Canvas

SwiftUI 的 app 模板來建立一個新的 Xcode 項目,而且瀏覽一下這個 canvas。微信

1.1 打開 Xcode ,在 Xcode 的啓動窗口中單擊 Create a new Xcode project ,或選擇 File > New > Projectapp

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()
    }
}
複製代碼

2. 自定義 Text View

爲了自定義 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()
    }
}
複製代碼

3. 用 Stacks 組合 View

在上一節建立標題 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 單擊 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()
    }
}
複製代碼

調整地點 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 ParkCalifornia ,這樣它們就會共享整個屏幕寬度。

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()
    }
}
複製代碼

4. 自定義 Image View

搞定名稱和位置 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 能夠當作一個蒙版的形狀,也能夠經過 strokefill 造成 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 strokecircle ,而後將其做爲 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

至此,咱們已準備好建立 map view 了,接下來使用 MapKit 中的 MKMapView 類來渲染地圖。

SwiftUI 中使用 UIView 子類,須要將其餘 view 包裝在遵循 UIViewRepresentable 協議的 SwiftUI view 中。 SwiftUI 包含了和 WatchKitAppKit 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:) 用來建立一個 MKMapViewupdateUIView(_: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 AgainResume 按鈕。

片刻以後,你會看到 Joshua Tree National Park 的地圖,這是 Turtle Rock 的故鄉。

6. 編寫詳情 View

如今咱們完成了所需的全部組件:名稱、地點、圓形圖片和地圖。

繼續使用目前的工具,將這些組件組合起來變成符合最終設計的詳情 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()
    }
}
複製代碼

相關文章
相關標籤/搜索