五個 Apple 平台,三個共用檔案:Return 如何真正落地跨平台 SwiftUI
Return 是我寫的冥想計時器,執行於五個 Apple 平台:iPhone、iPad、Mac、Apple Watch 與 Apple TV。1整個程式碼庫共有 40 個 Swift 檔案(不含測試)。其中只有三個檔案在五個平台之間真正共用。其餘皆拆分成獨立的 Xcode 目標,刻意讓 TimerManager、AudioManager、ContentView 這些概念重複出現,而非透過 #if os(...) 條件編譯來共用。
共用比例約為 7.5%,而且是刻意如此。
本文要談的是:在 2026 年,跨平台 SwiftUI 應用程式真正出貨的樣貌、為何積極共用程式碼其實被高估了,以及那三個真正共用的檔案有何共通點。
Return 鎖定的五個平台,正如 Apple 在 developer.apple.com 上所呈現。每一個都是 Xcode 中獨立的平台目標,而不是執行階段的分支。
重點摘要
- Return:主要目標 18 個 Swift 檔案(iOS + iPadOS + macOS)、tvOS 目標 10 個檔案、watchOS 目標 7 個檔案、widget 目標 2 個檔案(Live Activities),以及
Return/Shared/中真正跨平台的 3 個檔案。共計 40 個。 - 那三個共用檔案皆與持久化相關:
MeditationSession、SessionStore、SessionHistoryView。是透過 iCloud 流動的狀態,而非隨平台調適的 UI。 - tvOS 與 watchOS 都是獨立的 Xcode 目標,並非主要目標中的
#if os(tvOS)分支。控制模型差異太大,無法塞進同一個 ContentView。 - 即使在 iOS/iPadOS/macOS 的主要目標內,
#if os區塊仍然氾濫:ContentView.swift有 10 處、LiveActivityManager.swift有 8 處、VideoBackgroundView.swift有 8 處、AudioManager.swift有 6 處。 - 老實說:在五個 Apple 平台之間積極共用程式碼是一項維護負擔。一個小型共用核心(持久化層)加上各平台獨立的 UI,比一個塞滿
#if的巨型檔案更容易出貨、更不易出錯。
關於各平台的對應文章,請參閱 Apple 平台矩陣、watchOS 執行階段契約,以及 Liquid Glass SwiftUI 模式。
數據
剔除測試與 UI 測試後,依 Swift 檔案數計算的程式碼結構如下:
Return/ 18 files (iPhone + iPad + Mac, single target)
├── Shared/ 3 files ← cross-platform truth
│ ├── MeditationSession.swift
│ ├── SessionStore.swift
│ └── SessionHistoryView.swift
├── ContentView.swift (10 #if os branches)
├── TimerManager.swift (2 #if os branches)
├── AudioManager.swift (6 #if os branches)
├── HealthKitManager.swift
├── LiveActivityManager.swift (8 #if os branches, iOS-only)
├── ThemeManager.swift
├── VideoBackgroundView.swift (8 #if os branches)
├── GlassTextShape.swift (Liquid Glass, see prior post)
├── GlassTimerText.swift
└── … (settings, theme, audio assets, etc.)
ReturnTV/ 10 files (tvOS, separate target)
├── TVContentView.swift
├── TVTimerManager.swift ← duplicates main TimerManager
├── TVAudioManager.swift ← duplicates main AudioManager
├── TVDurationPicker.swift
├── TVFocusModifier.swift ← tvOS button styles for focus
├── TVSettingsView.swift
└── …
ReturnWatch Watch App/ 7 files (watchOS, separate target)
├── WatchContentView.swift
├── WatchTimerManager.swift ← duplicates main TimerManager
├── WatchAudioManager.swift ← duplicates main AudioManager
├── WatchHealthKitManager.swift ← duplicates main HealthKitManager (mostly)
├── WatchSettingsView.swift
└── …
ReturnWidgets/ 2 files (Live Activity + bundle)
├── ReturnLiveActivity.swift
└── ReturnWidgetsBundle.swift
五個平台、三個共用檔案、兩個各自獨立的平台專屬目標再加一個 widget 目標,主要目標內還大量使用條件編譯。共用比約為 7.5%。多數「多平台 SwiftUI」教學主張的方向恰恰相反:寫一個 ContentView,透過 @Environment(\.horizontalSizeClass) 與 #if os(...) 適應每一個平台。2這套對兩個平台(iPhone + iPad)行得通;到了五個平台就崩潰了。
那三個共用檔案的共通點
Return/Shared/MeditationSession.swift 定義了與 SwiftData 相鄰的值類型:3
struct MeditationSession: Codable, Identifiable, Equatable {
let id: UUID
let startDate: Date
let endDate: Date
let durationSeconds: Int
let sourceDevice: DeviceType
var syncedToHealthKit: Bool
enum DeviceType: String, Codable, CaseIterable {
case iPhone, iPad, mac, appleTV, appleWatch
}
}
該檔案的標頭註解承載了重要資訊:// Add this file to: Return, ReturnTV, ReturnWatch Watch App targets.同一份原始檔被三個 Xcode 目標共同引用,並非以符號連結處理,也未嵌入 Swift package。Apple 的建構系統樂於將同一個檔案編譯進三個二進位檔。
SessionStore.swift 是持久化層:對 NSUbiquitousKeyValueStore(Apple 的 iCloud Key-Value Store)的薄層封裝,負責讀寫 MeditationSession 陣列。這個選擇有其重要性:KV-store 同步讓 Return 不必自行佈署 CloudKit 容器即可獲得跨裝置的工作階段歷史,代價是整個 store 的容量上限為 1 MB。12每次工作階段平均只有幾百個位元組的冥想記錄而言,這個上限綽綽有餘。SessionHistoryView.swift 是一個渲染工作階段的 SwiftUI 列表。iPhone、iPad、Mac、Watch、TV 五個目標對這兩個檔案的使用方式完全相同。
這三個檔案的共通點:它們描述的是狀態,而不是互動。MeditationSession 在每個裝置上都是同一個概念。過往工作階段列表在每個裝置上的閱讀方式也相同。它們都不涉及控制介面、視窗管理員、音訊路由決策、focus 引擎或數位錶冠。一旦某個檔案需要知道自己跑在哪個平台上,它就不再可共用。
為何其餘部分沒有共用
以 TimerManager 為例。iOS/iPadOS/macOS 版本使用 Timer.publish(every: 1, ...),並透過 UserNotifications 派送通知。tvOS 版本(TVTimerManager)要處理使用者用 Siri Remote 暫停後螢幕保護程式啟動的情境。watchOS 版本(WatchTimerManager)則委派給 WKExtendedRuntimeSession(透過 WatchSessionManager),讓系統在螢幕變暗時仍能保持 app 反應靈敏,並透過數位錶冠而非觸控接收輸入。三個平台、三種根本不同的計時器行為。
當然可以將它們合而為一:class TimerManager { #if os(watchOS) ... #elif os(tvOS) ... }。結果會是一個有三種模式的類別,每種模式各四十行 #if 圍起來的程式碼,動到 iOS 路徑就有可能弄壞 watchOS 路徑。這是維護的惡夢。
三個各自獨立的類別、三個檔名,磁碟上的程式碼較多,腦中要記的程式碼卻較少。讀得懂的重複,勝過理解不了的抽象。
同樣的邏輯也適用於:
ContentViewvsTVContentViewvsWatchContentView:導覽模型不同(iPhone 是推進式、TV 是 focus 式、Watch 是列表式)。AudioManagervsTVAudioManagervsWatchAudioManager:音訊工作階段類別不同,watchOS 對背景音訊的限制更嚴,tvOS 對 AirPlay 的路由方式也不同。VideoBackgroundView在主要目標中有 8 個#if os(iOS)分支(並搭配一個#elseif os(macOS)分支),分別對應不同的影片素材(fire_phone.mp4與fire_mac.mp4)、不同的 layer 類型,以及不同的長寬比。4
我想補充一點:主要的 Return/ 目標確實把 iOS、iPadOS、macOS 包在一起了。這三個平台共用的程式碼比不共用的多。SwiftUI 的 NavigationStack 在三者皆可運作。.glassEffect() 在三者皆可運作。視窗管理上的差異雖然存在,但在同一個目標內仍可駕馭。tvOS 與 watchOS 才是我畫下「獨立目標」分界線的地方。
tvOS 案例:為何 focus 引擎逼出獨立目標
Apple TV 的導覽是圍繞 focus 引擎建構的。5使用者可互動的每一個 UI 元素都會宣告自己可被 focus;Siri Remote 的方向鍵在元素之間移動 focus;按下選擇鍵則啟動目前 focus 的元素。tvOS 上的 SwiftUI 透過 .focusable()、.focusEffect,以及自訂的 ButtonStyle(這些 ButtonStyle 會回應 @Environment(\.isFocused))來呈現 Apple 第一方 app 所使用的視差傾斜效果。TVFocusModifier.swift 中真正的生產程式碼如下:6
struct TVCapsuleButtonStyle: ButtonStyle {
var accentColor: Color = .white
@Environment(\.isFocused) private var isFocused
func makeBody(configuration: Configuration) -> some View {
configuration.label
.colorMultiply(isFocused ? focusedTextColor : accentColor)
.background(
Capsule().fill(isFocused
? AnyShapeStyle(accentColor)
: AnyShapeStyle(.ultraThinMaterial))
)
.clipShape(Capsule())
.scaleEffect(isFocused ? 1.1 : 1.0)
.scaleEffect(configuration.isPressed ? 0.95 : 1.0)
.shadow(color: .black.opacity(isFocused ? 0.3 : 0.1),
radius: isFocused ? 20 : 5, y: isFocused ? 10 : 2)
.animation(.easeInOut(duration: 0.2), value: isFocused)
}
}
同一個檔案也定義了 TVCircleButtonStyle,用於方形/圓形控制元件。兩種風格在 focus 時都會反轉顏色與半透明度:未 focus 的按鈕坐落於 .ultraThinMaterial 之上,focus 的按鈕則填滿 accent color,並放大尺寸與加深陰影。這個模式對此 app 而言在結構上就是 tvOS 專屬的。@Environment(\.isFocused) 在 iOS、iPadOS、macOS、watchOS 與 tvOS 上都可使用,13但只有在 tvOS 上,focus 驅動的導覽才是主要的互動模型,因為 Siri Remote 不會產生指標或觸控事件。在 iPhone 或 iPad 上,對應的控制元件是以點擊命中測試;在 Mac 上是以滑鼠懸停或點擊。TVFocusModifier.swift 中的按鈕風格假設 focus 是使用者最主要的可操作性,整套視覺回應都圍繞 focus 設計。要寫一個能在同一處處理 iOS 觸控、Mac 滑鼠懸停、tvOS focus 導覽的 ContentView,根本沒有好辦法。視圖結構真的不同:tvOS 的 ContentView 是一張可被 focus 的列構成的圖;iOS 的 ContentView 則是點擊即執行的堆疊。
時長選擇器也是同樣的情形。在 iPhone 上,它從底部滑上來並接受點擊。在 Apple TV 上,它是一橫排可被 focus 的格子,使用者用遙控器逐一導覽。TVDurationPicker.swift 之所以是獨立檔案,是因為以格子為基礎的 focus 設計在 iPhone 上沒有對應形態。硬把它們塞進同一個檔案,等於用 #if os(tvOS) 把兩個毫無關聯的 UI 黏在一起。
watchOS 案例:擴展執行階段工作階段、HealthKit 與更小的介面
watchOS 多了兩項其他平台沒有的結構限制:
WKExtendedRuntimeSession用於在 Apple Watch 螢幕變暗時保持 app 反應靈敏。8若沒有它,watchOS 會在每秒 tick 之間積極暫停 app,計時器會漂移。Return 在 watchOS 目標的Info.plist中宣告WKBackgroundModes: mindfulness,讓系統辨識此使用情境並授予執行階段預算;執行階段工作階段本身則以預設的WKExtendedRuntimeSession()初始化器建立。- 透過
NSUbiquitousKeyValueStore進行 iCloud 同步,而非 WatchConnectivity。Return 的工作階段歷史同步搭乘的是 iPhone、iPad、Mac 目標所使用的同一個 key-value store,因此在錶上記錄的冥想會出現在 iPhone 的歷史檢視中,無需任何錶到手機的直接訊息傳遞。WatchConnectivity 未來或許可作為即時狀態同步的選項,但 Return 選擇較簡單的模型:每個裝置都寫入同一個 iCloud KV-store,任何裝置下次讀取時都能看到聯集。
WatchTimerManager.swift 是錶端的計時器;它將擴展執行階段的工作委派給 WatchSessionManager,後者定義在 ReturnWatchApp.swift 中:final class WatchSessionManager: NSObject, WKExtendedRuntimeSessionDelegate。iOS 端的 TimerManager 沒有對應物,因為 iOS app 在前景時無需明確的執行階段工作階段也能保持反應。把錶端邏輯透過 #if os(watchOS) 塞進 iOS 的 TimerManager,意味著 iOS 程式碼路徑會匯入它從不使用的 WatchKit 符號,加上 watchOS 程式碼路徑需要 iOS 路徑沒有的初始化路徑。
WatchHealthKitManager.swift 是主要 HealthKitManager 的縮小版。它以同樣方式記錄正念分鐘數,但授權提示的 UX 不同(錶上無法顯示 HealthKitPermissionSheet)。Watch 端的類別大約是主要類別的一半大小。
iOS/iPadOS/macOS 主要目標內部發生了什麼
即便在主要目標內,共用也不是自動發生。ContentView.swift 有 10 個 #if os(macOS) 或 #if !os(macOS) 區塊;LiveActivityManager.swift 有 8 個;VideoBackgroundView.swift 有 8 個;AudioManager.swift 有 6 個。Live Activities 是 iPhone 專屬功能,所以整個 LiveActivityManager 都包在 #if os(iOS) 裡。iPhone 上的時長選擇器與 iPad、Mac 上的版面不同,因此 ContentView 中有並列的版面分支。
行得通的模式是:對於小型平台差異(不同的鍵盤行為、不同的內距、缺少的 API)使用 #if os(...);對於大型結構差異(focus vs. 觸控、鍛鍊工作階段 vs. 計時器),使用獨立目標。我最後採用的門檻是「分支超過約 10 行」。低於這個門檻,條件編譯沒問題。超過這個門檻,這個檔案就同時在做兩件事,第二件事該屬於另一個目標。
何時不該在五個平台都出貨
直白的評估如下。
若 app 資訊密度高,跳過 Apple Watch。46mm 螢幕容不下一個 30 項的列表、一個時長選擇器、再加一個設定頁。Return 在 watchOS 上能存活,是因為核心互動就是按一個按鈕(啟動/停止計時器)。生產力 app、財務 app,或媒體密集型 app 都做不到這點。
若 app 是互動式的,跳過 Apple TV。TV 適合環境式體驗(房間另一頭螢幕上跑著的計時器、音樂播放)。任何需要使用者頻繁輸入的東西都在跟平台對著幹。Return 上 tvOS,是因為「設一個 20 分鐘計時器,看著螢幕上的火」剛好是合適的環境式情境。記事 app 在 TV 上會讓人痛不欲生。
若 app 是手機優先介面,跳過 Mac。Mac 上的 SwiftUI 可以運作,但 NavigationStack 的推進模型相對於真正的 Mac sidebar 顯得像玩具。如果 app 在 Mac 上會顯得做得不夠,請出 Catalyst(會把 iPad app 轉換過去),或者在你還無法打造原生 Mac UI 之前,乾脆跳過 Mac。
若你還沒做尺寸類別適配,跳過 iPad。把 iPhone app 拉伸鋪滿 iPad 看起來就是廉價。iPad 至少要有帶 sidebar 的 NavigationSplitView;理想上要有真正的雙窗格版面。Return 在 iPad 上使用 split view,在 iPhone 上使用堆疊。程式碼在同一個目標,但 UI 確實不同。
我畫下的規則是:當 app 的核心互動能對應到某平台的輸入模型時,才在該平台出貨。冥想計時器上 Apple Watch(一按啟動)。冥想計時器上 Apple TV(設好就放著)。看板 app 兩者都別上。
哪些東西能毫不費力地通行
Return 中真正在五個平台都共用的三件事:
- 資料模型(
MeditationSession)。這個 struct 在每個平台都相同,透過NSUbiquitousKeyValueStore同步,任何平台都能讀取任何其他平台寫入的內容。 - 工作階段歷史檢視(
SessionHistoryView)。一個列出過往工作階段的List,在 iPhone、iPad、Mac、Apple Watch、Apple TV 上的渲染完全相同。SwiftUI 的List是少數能在五種尺寸上都優雅適配的原生元件之一。 - 持久化封裝層(
SessionStore)。讀寫操作與平台無關;底層儲存(NSUbiquitousKeyValueStore)在任何平台都是同一個 API。
三個概念。狀態、列表渲染、持久化。任何具狀態與展示性、且不涉及硬體專屬輸入模型的東西,都可以共用。任何觸及輸入、focus、音訊路由、螢幕尺寸或背景執行的東西,都不行。
這個模式也出現在 iOS Agent Development 指南中,我在那裡用不同的話語提出相同主張:iOS app 中可由 agent 寫的部分,與人類撰寫的部分共用大部分程式碼;需要人類判斷的部分(簽署、視覺打磨、效能)正好也是跨平台難以共用的部分。9這兩條邊界對齊。兩者談的都是領域知識從何處開始發揮作用。
多平台的代價
代價是不對稱的。為一個 iPhone app 加上 iPad,大概多出 20% 的程式碼(尺寸類別分支、某些位置的 split view)。在同一個目標再加上 Mac,又多 15-20%(#if os(macOS) 分支、選單列、視窗管理)。對小型 app 而言,每個主要目標約增加 10 個檔案。
Apple Watch 與 Apple TV 才是昂貴的部分。為 Return 加上 watchOS 需要在獨立目標中新增 11 個檔案,包括專屬的 audio、timer 與 HealthKit 管理員。加上 tvOS 又需要在另一個獨立目標中新增 10 個檔案,包括 focus 管理與自訂時長選擇器。兩者加起來幾乎讓 Swift 的程式碼面積翻倍——而從使用者功能層級來看,這還是同一個 app。
選擇在五個平台都出貨並非「為多平台而多平台」。每一項都是分別決定:選 Apple Watch 因為冥想計時器確實該長在手腕上,選 Apple TV 因為環境式螢幕格式適合在房間中進行的長時間工作階段,選 Mac 因為有些使用者會在會議空檔於辦公桌前冥想。每個平台都靠真實的使用情境贏得自己的目標。
如果某項功能無法靠自身贏得目標,較划算的做法是跳過該平台,把資源加倍投入在 app 表現卓越的那些平台上。
對你的 app 意味著什麼
三點要訣。
- 以「每個主要平台群一個目標」作為預設。iOS + iPadOS + macOS 放在一個目標裡可行,因為核心互動(觸控 + 游標)相近。tvOS 放在獨立目標。watchOS 放在獨立目標。每個獨立目標約多 10 個檔案,但能讓你免於陷入一個
#if分支無限增生的「上帝類別」。 - 積極共用狀態,而非互動。Codable 的模型 struct、持久化封裝層、
List的渲染幾乎可以零成本通行。Timer manager、audio manager、content view 不行。 - 每個平台都得自己賺到。不要因為「可以」就上 watchOS。當你的 app 核心互動能對應到該平台的輸入模型時再上。其餘的就跳過。
這個模式可以與我為同一系列 app 寫過的另外三個面向並行運作:用於 Apple Intelligence 的具型別 App Intents、用於跨 LLM agent 的 MCP 伺服器、面向裝置前人類的 Liquid Glass。同一條技術堆疊最外層的,就是平台:app 究竟能在哪些螢幕上執行。挑選平台時,請與挑選 AI 介面同樣審慎。
常見問答
為何不用 Swift package 來放共用程式碼?
我考慮過。為了三個檔案,Swift package 增加的儀式比省下的還多。Apple 的 Xcode 26 建構系統樂於將同一個原始檔編譯進多個目標——只要你勾選 Target Membership 框就行。Package 會多出獨立的 Package.swift、獨立的測試目標,以及每次重構都要繞過的一層間接。對小型共用核心而言,較簡單的答案勝出。10
SwiftData 在 watchOS 與 tvOS 上能用嗎?
SwiftData 可在 iOS 17+、macOS 14+、watchOS 10+、tvOS 17+ 使用,涵蓋 Return 的所有目標平台。11MeditationSession struct 是純粹的 Codable,不是 @Model,因為 Return 是用 NSUbiquitousKeyValueStore 來同步工作階段歷史,而非使用 SwiftData 容器。對 @Model 類型來說,模式是相同的:模型檔案共用,必要時各平台用不同的持久化容器。
我該用 Mac Catalyst,還是原生 Mac 目標?
當 iPad app 已經夠好、由 Catalyst 重建出的 Mac 版本讀起來像原生時,Catalyst 是合適的工具。Return 的主要目標是真正的多平台目標(不是 Catalyst),用 SwiftUI 在同一個二進位中建構 iOS、iPadOS、macOS。Mac UI 用 #if os(macOS) 渲染出與 iPad 不同的樣子:sidebar 取代 sheet、按鈕加上鍵盤對應字元等等。Catalyst 會更簡單,但 Mac UI 看起來會像跑在 Mac 上的 iPad app——而那正是 Catalyst 最為人所詬病的失敗模式。
對小型 app 而言,Apple TV 值得出貨嗎?
大概不值得。Apple TV app 有非常明確的使用情境(環境式、媒體、休閒遊戲)。如果你的 app 不屬於其中之一,平台的受眾太小,不足以正當化每個 app 多出的 10 個 Swift 檔案。Return 之所以特別鎖定 tvOS,是因為「在房間另一頭螢幕上進行長時間冥想工作階段」是少數能契合此平台、又與生產力相關的使用情境之一。
在五個平台都出貨要花多久?
很難給精確數字;視 app 而定。Return 從第一天起就以多平台出貨,而非逐步加上各平台,這比之後再回頭補上要快。粗略的經驗法則:iPhone-only MVP 加上 iPad 支援、再加上 Mac 支援,大約是 iPhone-only 時間的 1.5 倍。再加上 Apple Watch 多 0.5 倍。再加上 Apple TV 多 0.5 倍。所以五平台首發版本約為 iPhone-only 投入的 2.5 倍——但要附上一個但書:這次是 agent 輔助的開發,大多數重複程式碼是由 Claude Code 大量編輯而成,而非手動敲出來的。
參考來源
-
作者所開發的 Return,是於 2026 年 4 月 21 日上架 App Store 的冥想計時器 app。原生目標:iOS 26+、iPadOS 26+、macOS 26+、watchOS 26+、tvOS 26+。全 SwiftUI 撰寫。跨裝置工作階段歷史使用
NSUbiquitousKeyValueStore。 ↩ -
Apple Developer 的 Configuring a Multi-Platform App 與 WWDC 2024 上的 SwiftUI essentials 議程。Apple 的預設指引傾向於使用單一目標、以環境驅動的調適;本文採取的多目標路線是有意的偏離。 ↩
-
生產程式碼位於
Return/Return/Shared/MeditationSession.swift、SessionStore.swift、SessionHistoryView.swift。MeditationSession.swift的標頭註解寫著:「Add this file to: Return, ReturnTV, ReturnWatch Watch App targets.」 ↩ -
生產程式碼位於
Return/Return/VideoBackgroundView.swift(8 個#if os(iOS)分支再加一個#elseif os(macOS)分支)、Return/Return/ContentView.swift(10 個#if os分支)、Return/Return/AudioManager.swift(6 個#if os分支)、Return/Return/LiveActivityManager.swift(8 個#if os分支,整個檔案僅限 iOS)。分支數取自執行grep -Ec '^\s*#if os\\(' <file>的結果。 ↩ -
Apple Developer 的 Focus interactions Human Interface Guidelines。tvOS 的 focus 引擎是與 iOS 觸控、Mac 指標根本不同的導覽模型。 ↩
-
生產程式碼位於
Return/ReturnTV/TVFocusModifier.swift。定義了兩個ButtonStyle類型(TVCapsuleButtonStyle與TVCircleButtonStyle),透過@Environment(\.isFocused)在 focus 時反轉顏色與半透明度,並施加 scale 與 shadow。 ↩ -
Apple Developer 的 WatchConnectivity。是配對 iPhone 與 Apple Watch 通訊的框架;Return 並未用它做工作階段同步,而是仰賴 iCloud key-value store。 ↩
-
Apple Developer 的 WKExtendedRuntimeSession 與 Info.plist 中的 WKBackgroundModes 鍵。
mindfulness值的官方說明為:「Enables extended runtime sessions for silent meditation」——對冥想計時器再合適不過。Return 建立預設的WKExtendedRuntimeSession(),並在 watchOS 目標的Info.plist宣告WKBackgroundModes: mindfulness。生產程式碼:Return/ReturnWatch Watch App/ReturnWatchApp.swift定義了WatchSessionManager: NSObject, WKExtendedRuntimeSessionDelegate;WatchTimerManager.swift將擴展執行階段的工作委派給它。 ↩ -
作者於 Building iOS Apps with AI Agents 中的分析;這是涵蓋 8 個生產 app 的 agent 輔助 iOS 開發實務指南。 ↩
-
Apple Developer 的 Configuring a Multi-Platform App。Target membership 讓單一原始檔可編譯進多個目標,而無需 Swift package。對小型共用核心而言是合適的工具。 ↩
-
Apple Developer 的 SwiftData 平台可用性。可在 iOS 17+、iPadOS 17+、macOS 14+、watchOS 10+、tvOS 17+、visionOS 1+ 上使用,涵蓋全部五個 Apple 平台家族。 ↩
-
Apple Developer 的 NSUbiquitousKeyValueStore。Apple 的 iCloud Key-Value Store,用於在使用者各裝置間同步少量狀態。依 Apple 公告的限制,整個 store 在所有 key 上的總容量上限為 1 MB。生產程式碼:
Return/Return/Shared/SessionStore.swift。 ↩ -
Apple Developer 的
EnvironmentValues.isFocused。可在 iOS 14+、iPadOS 14+、macOS 11+、tvOS 14+、watchOS 7+ 上使用。API 本身是跨平台的;不同的是 focus 是否為使用者主要的導覽可操作性。 ↩