SwiftUI 專案怎麼開?實作教你設定畫面與啟用預覽

💬 這篇文章是我參加 2024 iThome 鐵人賽 時寫的,現在整理過後,重新放上部落格啦!

作為一個 UIKit 開發者,第一次打開 SwiftUI 的專案時,我的反應可以說是半熟悉半陌生😂。
有些結構名稱我看得懂,但用法又和以前不一樣。今天這篇,我會帶著自己的理解來整理 SwiftUI 專案的組成與邏輯,希望幫助像我一樣從 UIKit 轉來的新手,一起搞懂這個嶄新的開發世界。

SwiftUI 專案的基本結構

當我們在 Xcode 裡建立一個新的 SwiftUI 專案時,會自動生成一組預設檔案與資料夾。這些結構有些和 UIKit 相近,有些則是 SwiftUI 獨有的設計。我們就一個一個來看吧!

SwiftUI 專案基本架構

App.swift:整個 App 的起點

檔案名稱通常會是 [專案名]App.swift,像我現在的專案就叫 HandyInventoryApp.swift
這個檔案是 SwiftUI 專案的入口點,裡面會實作 App 協議,並使用 @main 標示它是啟動點。簡單範例如下:

Swift
import SwiftUI

@main
struct HandyInventoryApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

這段意思是:當 App 被打開時,ContentView 是使用者看到的第一個畫面。

ContentView.swift:UI 從這裡開始寫起

接下來是最熟悉的檔案之一:ContentView.swift。
它是一個遵守 View 協議的 struct,而不是像 UIKit 那樣繼承 UIViewController。

Swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .padding()
    }
}

#Preview {
    ContentView()
}

有幾個重點我一開始也不太懂,後來才慢慢釐清:

  • View 是一個協議(protocol),不是類別
  • body 是必須實作的屬性,用來定義 UI 要長怎樣
  • some View 是一種「不透明型別」(Opaque Type)

參考資料:

什麼是 some View?來聊聊不透明型別

我們看到 body 的回傳型別是 some View,這裡的 some 就是 Swift 中的不透明型別。
要簡單理解它,可以對照泛型的寫法:

Swift
func max<T>(_ x: T, _ y: T) -> T where T: Comparable

這段泛型函式定義的 T 是「輸入參數必須相同類型,但在定義時不知道具體是哪個型別」,這就是泛型。

而不透明型別剛好相反:你知道會回傳某個特定型別(像是一個具體的 View),但你不需要在函式定義裡寫出來,編譯器自己知道就好。

也就是說:

  • 泛型是「我不知道你是誰,但你們要一樣」
  • 不透明型別是「我知道你是誰,但我不告訴別人」

我覺得這個用健達出奇蛋比喻滿有趣的:你知道裡面會有玩具,但每次是什麼不重要、開箱才知道。這種隱藏具體實作的設計讓 SwiftUI 更彈性,也更易於模組化。

有興趣深入研究的話,推薦三篇我看過很棒的資源(大推 ChaoCode):

Preview:讓畫面變化一眼看穿的工具

在 SwiftUI 專案中,畫面右側的預覽區(Canvas)非常重要。
能即時顯示 UI 更新、幫助我們微調細節,而這個預覽功能的觸發點在於檔案下方的 #Preview 區塊:

Swift
#Preview {
    ContentView()
}

預覽畫面不是 App 的一部分,它不會被編進去實體 App 中,只存在開發階段。
如果不小心把 Canvas 關掉了,可以透過 Xcode 右上角的按鈕再打開,或使用快捷鍵 ⌥ + ⌘ + Enter 快速切換。

為什麼 SwiftUI 要用 Struct 寫 View?

這點我剛開始滿疑惑的,因為以前 UIKit 都是用 Class,現在突然全部改用 Struct,不知道的人真的會有點卡。查了一下資料後,我找到幾個滿合理的理由:

  1. 更安全:Struct 是值型別,不像 Class 是參考型別,能避免記憶體錯誤或不小心改到同一份資料的狀況
  2. 效能較佳:Struct 沒有繼承鏈,相對輕量
  3. 更好追蹤資料變化:SwiftUI 偏向「功能性編程」,不鼓勵隱性狀態,Struct 更符合這種風格
  4. 降低記憶體洩漏風險:避免 Strong Reference Cycles 的問題

這些特性其實和 SwiftUI 整體的設計哲學非常一致。

參考資料:Why does SwiftUI define views using Struct?

專案裡的其他重要檔案與資料夾

除了上面提到的主程式與畫面檔案,專案裡還有幾個值得認識的元素:

Persistence.swift

這個檔案與 Core Data 有關,是建立資料儲存邏輯的地方。雖然底層還是 SQLite,但透過這個層包裝,我們可以用物件導向的方式操作資料,不用自己寫 SQL,對資料庫不熟的人也能快速上手。

Assets.xcassets

這裡是 App 所有圖片、icon、顏色資源的家。
未來如果我們要加圖、換色主題,都會透過這裡來管理,並透過 Image("xxx")Color("yyy") 在畫面上呼叫出來。

Preview Content

這個資料夾不是拿來寫功能,而是專門提供預覽時的假資料。
如果我們要模擬 API 回傳的資料、假裝有某些圖片、測試不同狀態下的 UI,這裡就是幫手。它只存在於開發階段,不會進到正式版 App 中。

結構新,但其實不難懂

雖然 SwiftUI 的專案結構看起來跟 UIKit 有落差,但拆解後就會發現它其實非常直覺。進入點 App.swift、主畫面 ContentView.swift、Core Data 和資源資料夾各司其職,而且都設計得很一致。

老實說,我有種「雖然改變很多,但反而變簡單了」的感覺。明天我們將會開始認識各種 UI,讓我們繼續邊做邊學吧!

繼續看第四天 👉 SwiftUI 元件入門教學 – 文字、圖片、按鈕這樣用

分享這篇文章

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *