SwiftUI框架解析(一) —— 基于SwiftUI的一個簡單示例(一)

版本記錄

版本號 時間
V1.0 2019.06.14 星期五

前言

SwiftUI是2019年WWDC新推出的UI相關(guān)的API,相信大家都已經(jīng)知道并想體驗一下,下面我們就去一起了解和學(xué)習(xí)了。

開始

首先看下寫作環(huán)境

Swift 5, iOS 13, Xcode 11

在這個SwiftUI教程中,您將學(xué)習(xí)如何通過聲明和修改視圖來布局UI,以及如何使用狀態(tài)變量來更新UI。 您將使用Xcode的新預(yù)覽和實時預(yù)覽,體驗保持代碼和WYSIWYG布局同步的樂趣。

自Apple于2014年宣布推出Swift以來,SwiftUI是最激動人心的消息。這是邁向Apple實現(xiàn)每個人都可以編碼,簡化基礎(chǔ)知識的目標(biāo)邁出了一大步,因此您可以將更多時間花在滿足用戶需求的自定義功能上。一些開發(fā)人員開玩笑說他們可能被SwiftUI Sherlocked了!

SwiftUI允許您忽略Interface Builder(IB)storyboards,而無需編寫詳細(xì)的逐步說明來布置UI。 IBXcodeXcode 4之前是單獨的應(yīng)用程序,每次編輯IBActionIBOutlet的名稱或從代碼中刪除它時,接縫仍會顯示,并且您的應(yīng)用程序崩潰,因為IB沒有看到代碼更改?;蛘吣阋呀?jīng)對你必須在你的代碼中使用的segues或表格視圖單元格的字符串類型標(biāo)識符感興趣,但Xcode無法檢查你,因為它們是字符串。而且,雖然在WYSIWYG編輯器中設(shè)計新UI可能更快更容易,但在用代碼編寫時,復(fù)制或編輯UI會更有效率。

SwiftUI來了可以做好這點!您可以與其代碼并排預(yù)覽SwiftUI視圖 - 對一側(cè)的更改將更新另一側(cè),因此它們始終保持同步。沒有任何標(biāo)識符字符串出錯。它是代碼,但比你為UIKit編寫的要少得多,因此它更容易理解,編輯和調(diào)試。

SwiftUI不會取代UIKit - 比如SwiftObjective-C,你可以在同一個應(yīng)用程序中使用它們。您將無法在macOS上運行SwiftUI iOS應(yīng)用程序 - 這就是Catalyst。但是,SwiftUI API在不同平臺上是一致的,因此在多個平臺上使用相同的源代碼開發(fā)相同應(yīng)用程序?qū)⒏菀住?/p>

在本教程中,您將使用SwiftUIiOS Apprentice構(gòu)建我們著名的BullsEye游戲的變體。您將學(xué)習(xí)如何通過聲明和修改視圖來布局UI,以及如何使用狀態(tài)變量來更新UI。您將使用Xcode的一些新工具,尤其是預(yù)覽和實時預(yù)覽,并體驗保持代碼和WYSIWYG布局同步的樂趣。

注意:本教程假設(shè)您習(xí)慣使用Xcode開發(fā)iOS應(yīng)用程序。 你需要Xcode 11 beta。 要查看SwiftUI預(yù)覽,您需要macOS 10.15 beta。 因此,使用beta版軟件也需要一定的舒適度。

如果您沒有備用Mac,則可以 install Catalina beta on a separate APFS volume。

下載項目,并在RGBullsEye-Starter文件夾中構(gòu)建并運行UIKit應(yīng)用程序。 此游戲使用三個滑塊sliders - RGB顏色空間中的紅色,綠色和藍色值 - 以匹配目標(biāo)顏色。

我為RWDevCon 2016編寫了這個應(yīng)用程序,并在本教程中將代碼更新為Swift 5。 它運行在Xcode 10Xcode 11 beta中。 在本教程中,您將使用SwiftUI創(chuàng)建此游戲的基本版本。

Xcode 11 beta中,創(chuàng)建一個新的Xcode項目(Shift-Command-N),選擇iOS?Single View App,將項目命名為RGBullsEye,然后選中Use SwiftUI復(fù)選框:

將項目保存在RGBullsEye-Starter文件夾之外的某個位置。


Entering the New World of SwiftUI

在項目導(dǎo)航器中,打開RGBullsEye組以查看您的內(nèi)容:舊的AppDelegate.swift現(xiàn)在拆分為AppDelegate.swiftSceneDelegate.swiftSceneDelegate具有window

SceneDelegate不是特定于SwiftUI,但這一行是:

window.rootViewController = UIHostingController(rootView: ContentView())

UIHostingControllerSwiftUI視圖ContentView創(chuàng)建一個視圖控制器。

注意:UIHostingController使您可以將SwiftUI視圖集成到現(xiàn)有應(yīng)用程序中。 您可以在storyboard中添加一個Hosting View Controller,并從UIViewController創(chuàng)建一個segue。 然后按住Ctrl鍵從segue拖動到視圖控制器代碼中以創(chuàng)建IBSegueAction,您可以在其中指定hosting controllerrootView值 - SwiftUI視圖。

當(dāng)應(yīng)用程序啟動時,window會顯示ContentView的一個實例,該實例在ContentView.swift中定義。 它是一個符合View協(xié)議的struct

struct ContentView: View {
  var body: some View {
    Text("Hello World")
  }
}

這是SwiftUI聲明ContentView的主體body包含顯示Hello WorldText視圖。

DEBUG塊中,ContentView_Previews生成一個包含ContentView實例的視圖。

#if DEBUG
struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}
#endif

您可以在此處為預(yù)覽指定樣本數(shù)據(jù)。 但是這個預(yù)覽在哪里好像聽過?

代碼旁邊有一個很大的空白區(qū)域,位于頂部:

單擊Resume,稍等片刻以查看預(yù)覽:

注意:如果沒有看到Resume按鈕,請單擊editor options按鈕,然后選擇Editor and Canvas

如果您仍然沒有看到Resume按鈕,請確保您正在運行macOS 10.15 beta。


Outlining Your UI

您沒有看到的一個文件是Main.storyboard - 您將在SwiftUI代碼中創(chuàng)建您的UI,密切關(guān)注預(yù)覽以查看它的外觀。 但不要擔(dān)心 - 您不會編寫數(shù)百行代碼來創(chuàng)建視圖!

SwiftUI是聲明性的:您聲明了UI的外觀,SwiftUI將您的聲明轉(zhuǎn)換為可以完成工作的高效代碼。 Apple鼓勵您根據(jù)需要創(chuàng)建任意數(shù)量的視圖,以使代碼易于閱讀和維護。 特別推薦使用可重用的參數(shù)化視圖 - 就像將代碼提取到函數(shù)中一樣,您將在本教程的后面創(chuàng)建一個。

RGBullsEye的UI有很多子視圖,所以你首先要創(chuàng)建一個outline,使用Text視圖作為占位符。

首先用以下代碼替換Text(“Hello World”)

Text("Target Color Block")

如有必要,請單擊Resume以刷新預(yù)覽。

現(xiàn)在Command-Click預(yù)覽中的Text視圖,然后選擇Embed in HStack

請注意您的代碼更新以匹配:

HStack {
  Text("Target Color Block")
}

你在這里使用horizontal stack是因為你想要顯示目標(biāo)并且并排猜測顏色塊。

復(fù)制并粘貼Text語句,然后對其進行編輯,使HStack如下所示。 請注意,您不要使用逗號分隔兩個語句 - 只需在各自的行中寫入:

HStack {
  Text("Target Color Block")
  Text("Guess Color Block")
}

它在預(yù)覽中:

現(xiàn)在準(zhǔn)備通過在VStack中嵌入HStack來添加滑塊占位符 - 這次,在代碼中按命令單擊HStack

選擇Embed in VStack,出現(xiàn)新代碼,但預(yù)覽不會更改 - 您需要在顏色塊下面添加視圖。

HStack閉包下面打開一個新行,單擊工具欄中的+按鈕打開Library,然后將Vertical Stack拖到新行中:

正如您所期望的那樣,代碼和預(yù)覽更新:

注意:迷你地圖不會出現(xiàn)在我的屏幕截圖中,因為我隱藏了它:Editor ? Hide Minimap。

現(xiàn)在完成大綱,看起來像這樣:

VStack {
  HStack {
    Text("Target Color Block")
    Text("Guess Color Block")
  }

  Text("Hit me button")

  VStack {
    Text("Red slider")
    Text("Green slider")
    Text("Blue slider")
  }
}

新的VStack將包含三個滑塊sliders,顏色塊和滑塊之間會有一個按鈕。

注意:Xcode 11是測試版軟件,在編寫本教程時,很難弄清楚我做錯了什么。它有一些無用的錯誤消息,包括Unable to infer complex closure return type; add explicit type to disambiguate, ‘(LocalizedStringKey) -> Text’ is not convertible to ‘(LocalizedStringKey, String?, Bundle?, StaticString?) -> Text’ and Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type, which basically meant “huh??”。這些消息很少出現(xiàn)在我剛剛添加的代碼附近。如果您看到的消息似乎沒有幫助,或者指向以前完全正常的代碼,請嘗試注釋掉剛才添加的內(nèi)容。還要檢查拼寫并確保您的大括號和括號匹配。最后,在beta期間重新啟動Xcode總是一個好主意!


Filling in Your Outline

現(xiàn)在,練習(xí)你的新SwiftUI-fu技能,開始填充HStack顏色塊,所以它看起來像這樣:

HStack {
  // Target color block
  VStack {
    Rectangle()
    Text("Match this color")
  }
  // Guess color block
  VStack {
    Rectangle()
    HStack {
      Text("R: xxx")
      Text("G: xxx")
      Text("B: xxx")
    }
  }
}

每個顏色塊都有一個Rectangle。 目標(biāo)顏色塊在其Rectangle下方有一個Text視圖,而guess color block有三個Text視圖 - 在本教程的后面,您將替換每個xxx以顯示當(dāng)前滑塊值。

不要擔(dān)心黑色矩形占據(jù)場景 - 它們會為滑塊騰出空間,你現(xiàn)在就可以設(shè)置它們的前景顏色了。


Using @State Variables

您可以在SwiftUI中使用normal常量和變量,但是如果UI在其值發(fā)生更改時應(yīng)該更新,則將變量指定為@State變量。 這個游戲都是關(guān)于顏色的,所以影響guess rectangle顏色的所有變量都是@State變量。

struct ContentView的頂部,在body閉包上方添加這些行:

let rTarget = Double.random(in: 0..<1)
let gTarget = Double.random(in: 0..<1)
let bTarget = Double.random(in: 0..<1)
@State var rGuess: Double
@State var gGuess: Double
@State var bGuess: Double

R,G和B值介于0和1之間。將目標(biāo)值初始化為隨機值。 您也可以將猜測值初始化為0.5,但如果您沒有初始化某些變量,我會將它們保留為未初始化以顯示您必須執(zhí)行的操作。

向下滾動到DEBUG塊,該塊實例化要在預(yù)覽中顯示的ContentView。 初始化程序現(xiàn)在需要猜測值的參數(shù)值。 將ContentView()更改為:

ContentView(rGuess: 0.5, gGuess: 0.5, bGuess: 0.5)

創(chuàng)建滑塊時,它們將以居中顯示在預(yù)覽中。

您還必須修改SceneDelegate中的初始值,在scene(_:willConnectTo:options:),將ContentView()替換成下面這行:

window.rootViewController = UIHostingController(rootView:
  ContentView(rGuess: 0.5, gGuess: 0.5, bGuess: 0.5))

當(dāng)應(yīng)用加載其根場景時,滑塊將居中。

現(xiàn)在將前景色修改器添加到目標(biāo)Rectangle

Rectangle()
  .foregroundColor(Color(red: rTarget, green: gTarget, blue: bTarget, opacity: 1.0))

修改器.foregroundColor創(chuàng)建一個新的Rectangle視圖,現(xiàn)在使用由隨機生成的RGB值指定的前景顏色。

同樣,修改guess Rectangle

Rectangle()
  .foregroundColor(Color(red: rGuess, green: gGuess, blue: bGuess, opacity: 1.0))

當(dāng)R,G和B值均為0.5時,您會得到灰色。

單擊Resume,稍等片刻以進行預(yù)覽更新。

注意:預(yù)覽會定期刷新,以及單擊Resume或“實時預(yù)覽”按鈕時,所以不要驚訝地看到目標(biāo)顏色經(jīng)常變化。


Making Reusable Views

我向幾個人展示了這個游戲,他們發(fā)現(xiàn)它很容易讓人上癮 - 特別是平面設(shè)計師。 然后他們會要求我實現(xiàn)其他顏色空間之一,比如YUV。 但RGB是本教程的不錯選擇,因為滑塊基本相同,因此您將定義一個滑塊視圖,然后將其重用于其他兩個滑塊。

首先,假裝你沒有考慮重用,只需創(chuàng)建紅色滑塊。 在滑塊VStack中,用這個HStack替換Text(“Red slider”)占位符:

HStack {
  Text("0").color(.red)
  Slider(value: $rGuess, from: 0.0, through: 1.0)
  Text("255").color(.red)
}

您可以修改Text視圖以將文本顏色更改為紅色。 并且您使用值 - thumb的位置 - 在fromthrough值之間的范圍內(nèi)初始化Slider。

但是什么是$? 你剛剛適應(yīng)了嗎?! 對于期權(quán),現(xiàn)在是$?

對于這樣一個小符號來說,它實際上非??岫曳浅姶?。rGuess本身就是值 - 只讀。 $ rGuess是一個讀寫綁定 - 你需要它,在用戶更改滑塊的值時更新猜測矩形的前景色。

要查看差異,請在猜測矩形下方的三個Text視圖中設(shè)置值:

HStack {
  Text("R: \(Int(rGuess * 255.0))")
  Text("G: \(Int(gGuess * 255.0))")
  Text("B: \(Int(bGuess * 255.0))")
}

在這里,您只使用值而不是更改它們,因此您不需要$前綴。

注意:您和我知道滑塊從0變?yōu)?,但255結(jié)束標(biāo)簽和0到255 RGB值適用于您的用戶,他們可能會覺得更喜歡在0到255之間的RGB值,如十六進制顏色的表示。

等待預(yù)覽刷新,看到你的第一個滑塊:

為了騰出空間,顏色塊略有縮小,但是滑塊看起來仍然有點局促 - 末端標(biāo)簽太靠近窗口邊緣 - 所以在HStack中添加一些padding(另一個修改器):

HStack {
  Text("0").color(.red)
  Slider(value: $rGuess, from: 0.0, through: 1.0)
  Text("255").color(.red)
}.padding()

這就好多了

現(xiàn)在,如果您要復(fù)制粘貼編輯此HStack以創(chuàng)建綠色滑塊,則將.red更改為.green,將$ rGuess更改為$ gGuess。 這就是參數(shù)的所在。

Command-Click紅色滑塊HStack,然后選擇Extract Subview

這與Refactor ? Extract to Function的工作方式相同,但適用于SwiftUI視圖。

不要擔(dān)心出現(xiàn)的所有錯誤消息 - 當(dāng)您編輯完新的子視圖后,它們會消失。

命名ExtractedView ColorSlider,然后在body閉合之前在頂部添加這些行:

@Binding var value: Double
var textColor: Color

然后用$ value替換$ rGuess,用textColor替換.red

Text("0").color(textColor)
Slider(value: $value, from: 0.0, through: 1.0)
Text("255").color(textColor)

然后返回到VStack中對ColorSlider()的調(diào)用,并添加您的參數(shù):

ColorSlider(value: $rGuess, textColor: .red)

檢查預(yù)覽是否顯示紅色滑塊,然后復(fù)制粘貼編輯此行以將Text占位符替換為其他兩個滑塊:

ColorSlider(value: $gGuess, textColor: .green)
ColorSlider(value: $bGuess, textColor: .blue)

單擊Resume以更新預(yù)覽:

注意:您可能已經(jīng)注意到您經(jīng)常單擊Resume。 如果您不想從鍵盤上把手拿開,Option-Command-P將是您學(xué)習(xí)的最有用的鍵盤快捷鍵之一!

這就是整個應(yīng)用程序的完成!

現(xiàn)在有趣的事情:在預(yù)覽設(shè)備的右下角,點擊實時預(yù)覽(live preview)按鈕:

實時預(yù)覽可讓您與預(yù)覽進行交互,就像您的應(yīng)用程序在模擬器中運行一樣 - 太棒了!

等待預(yù)覽微調(diào)器(Preview spinner)停止,如有必要,請單擊Try Again。

現(xiàn)在移動那些滑塊以匹配顏色!

精彩! 您是否喜歡Goodnight Developers video from the WWDC Keynote中的程序員? 太令人滿意了!


Presenting an Alert

在使用滑塊獲得良好的顏色匹配后,您的用戶可以點擊Hit Me按鈕,就像在原始的BullsEye游戲中一樣。 就像在BullsEye中一樣,會出現(xiàn)一個Alert,顯示分?jǐn)?shù)。

首先,向ContentView添加一個方法來計算分?jǐn)?shù)。 在@State變量和body之間,添加以下方法:

func computeScore() -> Int {
  let rDiff = rGuess - rTarget
  let gDiff = gGuess - gTarget
  let bDiff = bGuess - bTarget
  let diff = sqrt(rDiff * rDiff + gDiff * gDiff + bDiff * bDiff)
  return Int((1.0 - diff) * 100.0 + 0.5)
}

diff值只是三維空間中兩點之間的距離 - 用戶的錯誤。 要獲得分?jǐn)?shù),請從1減去diff,然后將其縮放到100以外的值。較小的diff會產(chǎn)生較高的分?jǐn)?shù)。

接下來,使用Button視圖替換Text(“Hit me button”)占位符:

Button(action: {

}) {
  Text("Hit Me!")
}

Button有一個動作和一個標(biāo)簽,就像一個UIButton。 您希望發(fā)生的操作是提供Alert視圖。 但是如果你只是在Button動作中創(chuàng)建一個Alert,它將不會做任何事情。

而是將Alert創(chuàng)建為ContentView的子視圖之一,并添加Bool類型的@State變量。 然后,在希望顯示Alert時將此變量的值設(shè)置為true - 在這種情況下,在Button操作中。 當(dāng)用戶移除Alert時,該值將重置為false。

所以添加這個@State變量,初始化為false

@State var showAlert = false

然后將此行添加為Button操作:

self.showAlert = true

你需要self,因為showAlert在一個閉包內(nèi)。

最后,向Button添加一個presentation修飾符,這樣你的Button視圖如下所示:

Button(action: {
  self.showAlert = true
}) {
  Text("Hit Me!")
}
.presentation($showAlert) {
  Alert(title: Text("Your Score"), message: Text("\(computeScore())"))
}

您傳遞綁定$ showAlert,因為當(dāng)用戶移除alert時,其值將更改。

SwiftUI具有用于Alert視圖的簡單初始化器,就像許多開發(fā)人員在UIAlertViewController擴展中為自己創(chuàng)建的初始化器一樣。 這個有一個默認(rèn)的OK按鈕,所以你甚至不需要將它作為參數(shù)包含在內(nèi)。

關(guān)閉live preview,單擊Resume以刷新預(yù)覽,然后啟用實時預(yù)覽live preview,并嘗試匹配目標(biāo)顏色:

嘿,當(dāng)你有實時預(yù)覽時,誰需要iOS模擬器? 盡管如果您在模擬器中運行應(yīng)用程序,可以將其旋轉(zhuǎn)為橫向:

本教程幾乎沒有涉及SwiftUI的表面,但您現(xiàn)在已經(jīng)了解了如何使用一些新的Xcode工具來布局和預(yù)覽視圖,以及如何使用@State變量來更新UI。更不用說那個驚人的Alert了!

您現(xiàn)在已做好充分準(zhǔn)備,深入了解Apple的豐富資源 - 其教程和WWDC會議。教程tutorials和WWDC會議通過不同的示例項目。例如,Introducing SwiftUI#204)為會議構(gòu)建了一個列表應(yīng)用程序 - 它比教程的Landmarks應(yīng)用程序更簡單。 SwiftUI Essentials#216)向您展示了如何使用Form容器視圖輕松獲取iOS表單的外觀。

為了簡化本教程,我沒有為RGB顏色創(chuàng)建數(shù)據(jù)模型。但大多數(shù)應(yīng)用程序?qū)⑵鋽?shù)據(jù)建模為結(jié)構(gòu)體或類。如果需要SwiftUI來跟蹤模型實例中的更改,則必須通過實現(xiàn)發(fā)布更改事件的didChange屬性來符合BindableObject。看看Apple的示例項目,特別是Data Flow Through SwiftUI#226

為了使自己更容易進入SwiftUI,您可以將SwiftUI視圖添加到現(xiàn)有應(yīng)用程序,或者在新的SwiftUI應(yīng)用程序中使用現(xiàn)有視圖 - 觀看Integrating SwiftUI#231)以查看這樣做的快捷方式。

另外,瀏覽SwiftUI documentation以查看其他可用內(nèi)容 - 有很多內(nèi)容!

后記

本篇主要講述了SwiftUI相關(guān),感興趣的給個贊或者關(guān)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容