SwiftUI 動態 UI 編輯實作:執行時修改介面不需重新編譯

Published on: | Last updated:

嘿,今天要來聊一個我最近看到覺得蠻酷的東西。不知道你是不是也遇過這種鳥事,身為一個 iOS 開發者,最煩的日常之一,大概就是為了調個 UI 的小細節,改個數字,然後就要 Rebuild 整個專案... Command+R 按下去,等個幾十秒甚至幾分鐘,App 跑起來一看,「嗯,邊距多了 2pt,好像不太對」,然後再重複一次地獄循環。真的很消磨生命。

特別是跟設計師合作的時候,那更是災難。「欸這個圓角可以再圓一點嗎?」、「顏色好像深了一點點」,然後你就得一直改、一直 build、一直傳新版本。如果有一招可以直接在手機或模擬器上,像用 Photoshop 拉一樣,即時修改 UI,那該有多好?

欸,你別說,還真的有辦法。這不是什麼 Apple 官方大絕招,算是一個很聰明的 View Modifier 小技巧,但超級實用。

傳統開發 vs. 即時編輯的流程對比
傳統開發 vs. 即時編輯的流程對比

一句話結論

簡單講,就是用一個自幹的 SwiftUI View Modifier,包住任何你想要修改的 View,之後在 App 執行的時候,點一下或點兩下,就能叫出一個編輯面板,直接改顏色、邊距、字體大小,而且畫面會馬上更新,完全不用重跑 Xcode。

這招到底有多酷?看下去就知道

想像一下,你原本的 App 畫面長這樣,很普通,就是一些寫死的文字和圖示。

但只要你用了 `.editableAtRuntime()` 這個 modifier,在 App 跑起來之後,用手指在 iOS 上點一下(或是用滑鼠在 macOS 上點兩下),神奇的事情就發生了...

鏘鏘!會直接跳出一個編輯面板,讓你像個上帝一樣,即時調整這個元件的各種屬性:

  • 背景顏色:直接用 Color Picker 隨便你選。
  • 邊距 (Padding) & 圓角 (Corner Radius):拉動滑桿,馬上看到效果。
  • 透明度 (Opacity):從完全不透明到消失,自己決定。
  • 字體大小:文字要多大有多大。
  • 疊加一張圖片:甚至還能從相簿選一張圖蓋上去,透明度也能調!

老實說,這對工作流程的改善真的不是開玩笑的。特別是對於 UI/UX 的微調,根本是神器。設計師不用再看著截圖「通靈」,他可以直接在裝置上玩給你看:「欸,我覺得這個 padding 調到 12、圓角 8pt 最好看」,然後開發者只要把最後的數值記下來填回去就好。省下來的時間可以多喝好幾杯咖啡。

在 iOS 裝置上即時呼叫出的編輯面板
在 iOS 裝置上即時呼叫出的編輯面板

好啦,所以魔法是怎麼變出來的?

我知道你會想問,這背後到底是什麼黑魔法。其實原理還蠻單純的,核心就是 SwiftUI 的 ViewModifier。如果你對 `ViewModifier` 還不熟,可以把它想像成一個可以「重複使用」的「改裝套件」。Apple 官方文件對它有很多說明,但簡單來說就是你可以把一堆 `.padding()`、`.background()` 之類的修改器包成一大包,然後一次套用。

而這個方法的作者,只是把這個概念玩得更出神入化了一點。

第一步:用 ViewModifier 包裝一切

作者寫了一個叫做 RuntimeEditableModifier 的東西。然後用一個 extension 讓所有的 View 都可以很方便地呼叫它:


extension View {
    func editableAtRuntime() -> some View {
        modifier(RuntimeEditableModifier())
    }
}

所以之後你在任何 View,比如說一個 TextImage 後面,只要加上 .editableAtRuntime(),它就立刻獲得了「被即時編輯」的能力。超方便。

第二步:編輯面板與狀態綁定

這個 RuntimeEditableModifier 裡面做了幾件事:

  1. 它用一個 @State 變數去儲存一堆樣式值,像是背景色、padding 大小等等(作者把它包在一個叫 ViewStyle 的 struct 裡)。
  2. 它偵測使用者的點擊手勢(iOS 是 onTapGesture,macOS 是點兩下)。
  3. 當手勢觸發時,把一個 showEditor@State 布林值變成 true
  4. .sheet() 或其他 modal 方式,根據 showEditor 的狀態,來決定要不要跳出那個編輯面板。
  5. 最重要的,那個編輯面板裡的滑桿、顏色選擇器,全部都跟第一步提到的那個 ViewStyle 狀態變數「雙向綁定」。所以你一動面板,@State 就變了,畫面就跟著重繪。

這就是 SwiftUI 迷人的地方,這種宣告式的 UI 框架,只要狀態一變,UI 就會自動更新,根本不用像以前 UIKit 還要手動去改元件的 property。

給你看一下編輯面板的核心程式碼,你會發現它就是很單純的 SwiftUI `Form`:


// 編輯器裡的一小段,就是很標準的 SwiftUI
Section(header: Text("Background")) {
    // 顏色選擇器,直接綁定 style 裡的 backgroundColor
    ColorPicker("Background Color", selection: $style.backgroundColor)
    
    // 透明度滑桿,綁定 style 裡的 opacity
    Slider(value: $style.opacity, in: 0...1) {
        Text("Opacity")
    }
}

你看,沒什麼黑魔法,就是把 SwiftUI 的基本功組合起來而已。但這個組合的點子本身就價值千金啊。

OK,但什麼時候該用?跟其他方法比起來呢?

這招雖然酷,但你可能會想,我還有 Server-Driven UI (由後端決定 App 長相) 這種更猛的招式,為什麼要用這個?我自己是覺得,它們的應用場景不太一樣。我整理了一個簡單的比較表,讓你感受一下差異。

比較項目 即時修改 Modifier (這篇的方法) 伺服器驅動 UI (Server-Driven UI) 寫死在程式碼裡 (Hardcoding)
開發速度 超快!特別是微調 UI 的時候,根本是光速。 前期建構框架很慢,但後期要改 UI 就不用更新 App。 最慢,改個顏色就要重跑一次,等到天荒地老。
彈性 中等。只能改 Modifier 能動的屬性,沒辦法無中生有變出一個新按鈕。 超高!理論上整個頁面都能由後端重新組合,彈性最大。 最低。寫成怎樣就是怎樣,動彈不得。
效能影響 有一點點。畢竟多了偵測手勢和一堆 @State,但主要是在 Debug 階段用,影響不大。 看架構。可能會增加網路請求和客戶端的解析負擔。 最好。因為所有東西都編譯好了,執行效率最高。
適合情境 開發/測試階段、內部工具、給 PM 或設計師用的 Demo 版。 需要頻繁變更畫面的電商 App、活動頁面、A/B 測試框架 App 裡不太會變動的核心功能、需要最高效能的頁面。

所以你看,這個 Modifier 的方法,它的定位就很清楚:它是一個超強的「開發輔助」與「原型製作」工具

我能想到幾個超適合的場景:

  • 使用者自訂佈景主題:如果你的 App 想做一個讓使用者自己換主題顏色、字體大小的功能,這個架構幾乎是現成的!只是要把最後的設定值存起來而已。
  • 內部測試版:發一個有這個功能的版本給 QA 或 PM,他們發現 UI bug 或想調整,可以直接在上面調好、截圖給你,連溝通成本都省了。
  • 快速 A/B 測試框架:想測試紅色按鈕跟綠色按鈕哪個點擊率高?用這個可以快速弄出不同版本,不用真的寫兩套 code。
修改前後的 UI 對比,從普通按鈕到客製化按鈕的蛻變
修改前後的 UI 對比,從普通按鈕到客製化按鈕的蛻變

不過呢,這招不是萬靈丹,有幾個陷阱要注意

說了這麼多好話,還是要來點老實說。這方法雖然很棒,但不是沒有代價的,你在用之前最好想清楚幾件事。

第一個就是效能。雖然 SwiftUI 很聰明,但你在每個 View外面都包一層 `ZStack`、手勢偵測、還有一堆 `@State` 變數... 當你的畫面變得很複雜,元件一多,肯定會對效能造成一些影響。所以把整個 App 所有元件都包上這個...呃,我自己是不會這樣做啦。

第二個是適用範圍。這招很明顯是給「開發階段」和「內部測試」用的。你總不希望 App 上架之後,使用者在那邊亂點,結果不小心點到觸發區,跳出一個他看不懂的編輯面板,然後把你的 UI 搞得亂七八糟吧? 😂 所以如果要放到正式產品裡,一定要有開關可以徹底關閉這個功能,或是用編譯旗標(Compilation Flags)讓它只在 Debug 模式下生效。

說到這個,就讓我想到之前在台灣的 iOS 開發者社群,像是 iPlayground 之類的活動,常常會看到有人分享自己寫的各種 debug 小工具,精神上是完全一樣的:犧牲一點點效能,換來開發效率的巨大提升。這就是工程師的智慧啊。

最後,這招能改的,也只限於 SwiftUI Modifier 能動的範圍。像是改顏色、邊距、字型,這些都很簡單。但如果你想「把一個 Text 換成 Image」,或是「在 VStack 裡動態增加三個按鈕」,那這個方法就做不到了。那個需要動到真正的視圖結構(View hierarchy),就得靠 Server-Driven UI 或其他更複雜的作法了。

總結來說,把它當成一個瑞士刀裡的開罐器,非常有用,但不要想用它來蓋房子。知道它的極限在哪,就能發揮它最大的價值。

好啦,今天就差不多分享到這邊。我自己是覺得這個點子真的很棒,充分利用了 SwiftUI 的特性去解決一個開發上的痛點。下次你又在為了調那 2pt 的邊距而等到不耐煩時,或許可以試試看這個方法。

對了,除了改顏色和 padding,你覺得還有什麼 UI 屬性,是你最希望能即時調整的?在下面留言分享你的看法吧!

Related to this topic:

Comments