iOS 開發 #11 | SwiftUI ( 1 )

SwiftUI 是什麼

Han
彼得潘的 Swift iOS / Flutter App 開發教室
9 min readSep 26, 2024

--

前言

在開發時發現 SwiftUI 這個好玩的東西,接觸了一段時間後該把這段時間學的東西記錄下來

圖源:What’s new in SwiftUI — WWDC21 — Videos — Apple Developer

契機

在一開始學習開發 iOS 時是使用 storyboard 來設計 UI 介面,但在設定 Auto Layout 時一直出現紅色的警告,看了警告訊息也看不出哪邊有問題,且即使在 storyboard 設計完 UI 介面後還得拉線拉到 ViewController 裡面,拉著拉著在 ViewController 的 Code 就越來越多,那你說不想拉線不也可以用 Code 來產生 UI 元件嗎,沒錯是可以用 Code 來產生 UI 元件,但設計個畫面得打多少 Code 啊,如果我懶癌發作又把元件跟控制的 Code 都寫在一起那畫面會有多混亂我可不敢想

想一想在 iOS 中有沒有像 Flutter 、Jetpack Compose 那種聲明式的框架?ㄟ恭喜在 iOS 中還真有這種框架,那就是 SwiftUI

SwftUI

SwiftUI 是一種聲明式框架。聲明式風格的核心思想是描述界面的最終狀態,然後讓框架負責在狀態發生變化時自動更新 UI

  1. 聲明式語法:
    開發者定義 UI 的最終狀態,而不是描述如何一步步構建 UI
    UI 元素和其狀態緊密結合,當狀態改變時,UI 自動更新
  2. 數據驅動:
    使用狀態管理(如 @State@Binding@ObservedObject 等)來自動刷新 UI
  3. 可組合性:
    UI 元素是可以組合的,構建複雜界面時可以將小的 UI 組件組合成大的 UI 組件
import SwiftUI

struct ContentView: View {
@State private var counter = 0

var body: some View {
// HomePage()
VStack {
Text("Counter: \(counter)")
.padding()
Button(action: {
counter += 1
}) {
Text("Increment")
}
}
}
}

#Preview {
ContentView()
}

這邊用計數器來做介紹

struct ContentView: View

ContentView 這個結構遵守了 View 協定,這意味著它是 SwiftUI 中的一個 View

這是 SwiftUI 的核心概念之一:每個視圖都是一個結構,且必須遵守 View 協定

var body: some View

body 屬性會返回某種類型的 View,但具體是哪一種類型可以隱藏起來

這利用了 Swift 的 opaque return types 概念,即它告訴編譯器 body 會返回一個遵循 View 協定的視圖,但不必明確指定返回的具體視圖類型

var body: some View 如何運作

當 SwiftUI 需要顯示 ContentView 的時候,它會檢查 body 屬性,並渲染其中描述的視圖結構。在這個例子中,body 的值是一個 VStack,裡面有一個 Text 和一個 Button,這些視圖組合起來形成了 ContentView 的界面

介紹完 struct ContentView: View & var body: some View 後,接著來介紹裡面的 UI 元件

Text

這是 SwiftUI 中顯示文字的 View

Text("這邊放你要顯示的文字")

常用的幾個參數 :

  • .font(_:):設定字體大小和風格
  • .foregroundStyle(_:):設定文字顏色
  • .padding(_:):設定內邊距
  • .background(_:):設定背景顏色
  • .bold():加粗文字
  • .italic():文字斜體
  • .lineLimit(_:):設定行數限制
  • .multilineTextAlignment(_:):設定多行文字對齊方式

Button

這個相當於 UIButtonUIButton 是什麼應該不用我介紹吧……嗎?

Button(action: {
print("Button tapped")
}) {
Text("Tap Me")
.padding() // 設定內邊距
.background(Color.blue) // 設定背景顏色
.foregroundColor(.white) // 設定文字顏色
.cornerRadius(8) // 設定圓角
}

action 是用來設定按鈕的點擊事件,而 {} 裡面是用來設定按鈕的 View,例如這個例子是一個圓角半徑為 8 、背景為藍色、裡面的字體顏色為白色的按鈕,按了會 print Button tapped

Button 中,我的理解是 Button 是賦予子物件 Button 的特性,如 action ,而 label{} 裡面的 view 是設定這個 button 的畫面,所以在上面的例子中那些 Modifier 其實是在設定 Text 的樣式

如果要做出像 Android 或 Flutter 的 Card 的樣式,可以用 VStackHStackZStack 搭配 Modifier 來設計成 Card

幾個常用的 Modifier

  • .padding(_:):設定內邊距
  • .background(_:):設定背景顏色
  • .cornerRadius(_:):設定圓角
  • .shadow(color:radius:x:y:):設定陰影

.cornerRadius(_:) 已被棄用,如果要設定圓角可以使用 .clipShape(RoundedRectangle(cornerRadius: CGFloat))

介紹完比較簡單的元件後,來介紹跟排版有關的元件

這邊會把五種跟排版有關的元件都介紹一次

VStack

VStack 用來垂直排列子物件

VStack(alignment: .center, spacing: 10) {
Text("First")
Text("Second")
Text("Third")
}

常用參數

  • alignment:子視圖的水平對齊方式。可選值有 .leading.center.trailing
  • spacing:子視圖之間的垂直間距

HStack

HStack 用於水平排列子視圖

HStack(alignment: .center, spacing: 10) {
Text("First")
Text("Second")
Text("Third")
}

常用參數

  • alignment:子視圖的垂直對齊方式。可選值有 .top.center.bottom
  • spacing:子視圖之間的水平間距

ZStack

ZStack 用於將子視圖堆疊在一起,從後到前顯示

ZStack(alignment: .center) {
Color.red
Text("Hello")
Text("World")
}

在這邊我放了兩個 Text,這樣比較好知道這個元件所做的事

常用參數

  • alignment:子視圖的對齊方式。可選值有 .top.bottom.center.leading.trailing

GeometryReader

GeometryReader 用於獲取父視圖的幾何信息,允許動態調整子視圖的大小和位置

GeometryReader { geometry in
VStack {
Text("Width: \(geometry.size.width)")
Text("Height: \(geometry.size.height)")
}
}

Spacer

Spacer 用於在容器內創建可變空間,通常用於推動其他視圖

HStack {
Text("Left")
Spacer()
Text("Right")
}

這邊可以看到 LeftRightSpacer 給擠到螢幕兩側

在上面的介紹中能發現在 VStack 中只能設定 .leading.center.trailing 這三種水平對齊,而 HStack 中只能設定 .top.center.bottom 這三種垂直對齊

由於在 SwiftUI 的 UI 編排方式是用堆疊的方式, VStack 會將子視圖從上到下進行堆疊,並且 alignment 參數決定了每個子視圖在水平軸上的對齊方式。而 HStack 會將子視圖從左到右進行堆疊,並且 alignment 參數決定了每個子視圖在垂直軸上的對齊方式

如果想要在 VStack 中控制子視圖的對齊方式,並且希望有更細緻的控制,可以考慮使用其他容器視圖,如 HStackGeometryReader,這樣可以更靈活地進行布局和對齊。如果想要將視圖推到上面或下面,可以使用 Spacer。在 SwiftUI 中,對於需要更複雜的對齊和布局,可以使用 Spacer 和其他容器視圖來達到需求

那介紹了元件後,我們回頭來看看上面提到的計數器

import SwiftUI

struct ContentView: View {
@State private var counter = 0

var body: some View {
// HomePage()
VStack {
Text("Counter: \(counter)")
.padding()
Button(action: {
counter += 1
}) {
Text("Increment")
}
}
}
}

#Preview {
ContentView()
}

會發現 Code 中有一個 @State

@State 是 SwiftUI 中最基本的狀態管理屬性包裝器。當 @State 標記的值發生變化時,SwiftUI 會自動重新計算依賴這個狀態的視圖,並刷新它們

當按下按鈕時,counter 值變化,SwiftUI 會自動重新計算並刷新顯示 counterText 視圖

後記

這個 SwiftUI 系列會介紹我在使用 SwiftUI 開發時所發現的事物或在開發時遇到的問題,如果我的懶癌沒發作且有發現新東西的話這系列應該會連載吧……嗎?

--

--