Return 是我寫的冥想計時器,執行於五個 Apple 平台:iPhone、iPad、Mac、Apple Watch 與 Apple TV。1整個程式碼庫共有 40 個 Swift 檔案(不含測試)。其中只有三個檔案在五個平台之間真正共用。其餘皆拆分成獨立的 Xcode 目標,刻意讓 TimerManagerAudioManagerContentView 這些概念重複出現,而非透過 #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 個。
  • 那三個共用檔案皆與持久化相關:MeditationSessionSessionStoreSessionHistoryView。是透過 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/18files(iPhone+iPad+Mac,singletarget)
├──Shared/3filescross-platformtruth
│├──MeditationSession.swift
│├──SessionStore.swift
│└──SessionHistoryView.swift
├──ContentView.swift(10#ifosbranches)
├──TimerManager.swift(2#ifosbranches)
├──AudioManager.swift(6#ifosbranches)
├──HealthKitManager.swift
├──LiveActivityManager.swift(8#ifosbranches,iOS-only)
├──ThemeManager.swift
├──VideoBackgroundView.swift(8#ifosbranches)
├──GlassTextShape.swift(LiquidGlass,seepriorpost)
├──GlassTimerText.swift
└──…(settings,theme,audioassets,etc.)

ReturnTV/10files(tvOS,separatetarget)
├──TVContentView.swift
├──TVTimerManager.swiftduplicatesmainTimerManager
├──TVAudioManager.swiftduplicatesmainAudioManager
├──TVDurationPicker.swift
├──TVFocusModifier.swifttvOSbuttonstylesforfocus
├──TVSettingsView.swift
└──…

ReturnWatchWatchApp/7files(watchOS,separatetarget)
├──WatchContentView.swift
├──WatchTimerManager.swiftduplicatesmainTimerManager
├──WatchAudioManager.swiftduplicatesmainAudioManager
├──WatchHealthKitManager.swiftduplicatesmainHealthKitManager(mostly)
├──WatchSettingsView.swift
└──…

ReturnWidgets/2files(LiveActivity+bundle)
├──ReturnLiveActivity.swift
└──ReturnWidgetsBundle.swift

五個平台、三個共用檔案、兩個各自獨立的平台專屬目標再加一個 widget 目標,主要目標內還大量使用條件編譯。共用比約為 7.5%。多數「多平台 SwiftUI」教學主張的方向恰恰相反:寫一個 ContentView,透過 @Environment(\.horizontalSizeClass)#if os(...) 適應每一個平台。2這套對兩個平台(iPhone + iPad)行得通;到了五個平台就崩潰了。

那三個共用檔案的共通點

Return/Shared/MeditationSession.swift 定義了與 SwiftData 相鄰的值類型:3

structMeditationSession:Codable,Identifiable,Equatable{
letid:UUID
letstartDate:Date
letendDate:Date
letdurationSeconds:Int
letsourceDevice:DeviceType
varsyncedToHealthKit:Bool

enumDeviceType:String,Codable,CaseIterable{
caseiPhone,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 路徑。這是維護的惡夢。

三個各自獨立的類別、三個檔名,磁碟上的程式碼較多,腦中要記的程式碼卻較少。讀得懂的重複,勝過理解不了的抽象。

同樣的邏輯也適用於:

  • ContentView vs TVContentView vs WatchContentView:導覽模型不同(iPhone 是推進式、TV 是 focus 式、Watch 是列表式)。
  • AudioManager vs TVAudioManager vs WatchAudioManager:音訊工作階段類別不同,watchOS 對背景音訊的限制更嚴,tvOS 對 AirPlay 的路由方式也不同。
  • VideoBackgroundView 在主要目標中有 8 個 #if os(iOS) 分支(並搭配一個 #elseif os(macOS) 分支),分別對應不同的影片素材(fire_phone.mp4fire_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

structTVCapsuleButtonStyle:ButtonStyle{
varaccentColor:Color=.white
@Environment(\.isFocused)privatevarisFocused

funcmakeBody(configuration:Configuration)->someView{
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 多了兩項其他平台沒有的結構限制:

  1. WKExtendedRuntimeSession 用於在 Apple Watch 螢幕變暗時保持 app 反應靈敏。8若沒有它,watchOS 會在每秒 tick 之間積極暫停 app,計時器會漂移。Return 在 watchOS 目標的 Info.plist 中宣告 WKBackgroundModes: mindfulness,讓系統辨識此使用情境並授予執行階段預算;執行階段工作階段本身則以預設的 WKExtendedRuntimeSession() 初始化器建立。
  2. 透過 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 中真正在五個平台都共用的三件事:

  1. 資料模型(MeditationSession)。這個 struct 在每個平台都相同,透過 NSUbiquitousKeyValueStore 同步,任何平台都能讀取任何其他平台寫入的內容。
  2. 工作階段歷史檢視(SessionHistoryView)。一個列出過往工作階段的 List,在 iPhone、iPad、Mac、Apple Watch、Apple TV 上的渲染完全相同。SwiftUI 的 List 是少數能在五種尺寸上都優雅適配的原生元件之一。
  3. 持久化封裝層(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 意味著什麼

三點要訣。

  1. 以「每個主要平台群一個目標」作為預設。iOS + iPadOS + macOS 放在一個目標裡可行,因為核心互動(觸控 + 游標)相近。tvOS 放在獨立目標。watchOS 放在獨立目標。每個獨立目標約多 10 個檔案,但能讓你免於陷入一個 #if 分支無限增生的「上帝類別」。
  2. 積極共用狀態,而非互動。Codable 的模型 struct、持久化封裝層、List 的渲染幾乎可以零成本通行。Timer manager、audio manager、content view 不行。
  3. 每個平台都得自己賺到。不要因為「可以」就上 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 大量編輯而成,而非手動敲出來的。

參考來源


  1. 作者所開發的 Return,是於 2026 年 4 月 21 日上架 App Store 的冥想計時器 app。原生目標:iOS 26+、iPadOS 26+、macOS 26+、watchOS 26+、tvOS 26+。全 SwiftUI 撰寫。跨裝置工作階段歷史使用 NSUbiquitousKeyValueStore。 

  2. Apple Developer 的 Configuring a Multi-Platform App 與 WWDC 2024 上的 SwiftUI essentials 議程。Apple 的預設指引傾向於使用單一目標、以環境驅動的調適;本文採取的多目標路線是有意的偏離。 

  3. 生產程式碼位於 Return/Return/Shared/MeditationSession.swiftSessionStore.swiftSessionHistoryView.swiftMeditationSession.swift 的標頭註解寫著:「Add this file to: Return, ReturnTV, ReturnWatch Watch App targets.」 

  4. 生產程式碼位於 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> 的結果。 

  5. Apple Developer 的 Focus interactions Human Interface Guidelines。tvOS 的 focus 引擎是與 iOS 觸控、Mac 指標根本不同的導覽模型。 

  6. 生產程式碼位於 Return/ReturnTV/TVFocusModifier.swift。定義了兩個 ButtonStyle 類型(TVCapsuleButtonStyleTVCircleButtonStyle),透過 @Environment(\.isFocused) 在 focus 時反轉顏色與半透明度,並施加 scale 與 shadow。 

  7. Apple Developer 的 WatchConnectivity。是配對 iPhone 與 Apple Watch 通訊的框架;Return 並未用它做工作階段同步,而是仰賴 iCloud key-value store。 

  8. 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 將擴展執行階段的工作委派給它。 

  9. 作者於 Building iOS Apps with AI Agents 中的分析;這是涵蓋 8 個生產 app 的 agent 輔助 iOS 開發實務指南。 

  10. Apple Developer 的 Configuring a Multi-Platform App。Target membership 讓單一原始檔可編譯進多個目標,而無需 Swift package。對小型共用核心而言是合適的工具。 

  11. Apple Developer 的 SwiftData 平台可用性。可在 iOS 17+、iPadOS 17+、macOS 14+、watchOS 10+、tvOS 17+、visionOS 1+ 上使用,涵蓋全部五個 Apple 平台家族。 

  12. Apple Developer 的 NSUbiquitousKeyValueStore。Apple 的 iCloud Key-Value Store,用於在使用者各裝置間同步少量狀態。依 Apple 公告的限制,整個 store 在所有 key 上的總容量上限為 1 MB。生產程式碼:Return/Return/Shared/SessionStore.swift。 

  13. Apple Developer 的 EnvironmentValues.isFocused。可在 iOS 14+、iPadOS 14+、macOS 11+、tvOS 14+、watchOS 7+ 上使用。API 本身是跨平台的;不同的是 focus 是否為使用者主要的導覽可操作性。 

相關文章