← 所有文章

五個 Apple 平台,三個共用檔案:Return 如何真正落地跨平台 SwiftUI

Return 是我寫的冥想計時器,執行於五個 Apple 平台:iPhone、iPad、Mac、Apple Watch 與 Apple TV。1整個程式碼庫共有 40 個 Swift 檔案(不含測試)。其中只有三個檔案在五個平台之間真正共用。其餘皆拆分成獨立的 Xcode 目標,刻意讓 TimerManagerAudioManagerContentView 這些概念重複出現,而非透過 #if os(...) 條件編譯來共用。

共用比例約為 7.5%,而且是刻意如此。

本文要談的是:在 2026 年,跨平台 SwiftUI 應用程式真正出貨的樣貌、為何積極共用程式碼其實被高估了,以及那三個真正共用的檔案有何共通點。

iOS 26 platform tile from Apple Developer iPadOS 26 platform tile from Apple Developer macOS 26 platform tile from Apple Developer watchOS 26 platform tile from Apple Developer tvOS 26 platform tile from Apple Developer

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/                            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 路徑。這是維護的惡夢。

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

同樣的邏輯也適用於:

  • 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

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 多了兩項其他平台沒有的結構限制:

  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 是否為使用者主要的導覽可操作性。 

相關文章

Apple平台矩陣:哪些目標平台值得擁有哪種App

iOS、iPad、Mac、Watch、Vision、TV。六個平台,六種義務。挑選Apple目標平台是產品決策,而非工程決策。

1 分鐘閱讀

iOS 26 上的 HealthKit + SwiftUI:授權、樣本類型,以及兩款上架 App 的跨平台模式

來自 Water(飲水追蹤、HKQuantitySample)與 Return(正念冥想、HKCategorySample)的真實生產級模式。權限體驗、async 包裝、watchOS 變體,以及務必避開的陷阱。

5 分鐘閱讀

迴圈工程:在驗證成本低廉之處,迴圈才能取勝

以 Boris Cherny 的完整逐字稿驗證迴圈工程:他點名的每一個迴圈,驗證成本都很低廉。這項限制決定了什麼適合自動化。

4 分鐘閱讀