五個 Apple 平台,三個共用檔案:Return 如何真正做出跨平台 SwiftUI
我的冥想計時器 Return 跑在五個 Apple 平台上:iPhone、iPad、Mac、Apple Watch 與 Apple TV。1 這個程式庫共有 40 個 Swift 檔案(不含測試)。其中只有三個由五個平台共用。 其餘的則拆進各自的 Xcode target,寧可重複實作 TimerManager、AudioManager、ContentView 這些概念,也不用 #if os(...) 條件編譯把它們湊在一起。
共用比例約 7.5%,而這是刻意的。
這篇文章要談的是:2026 年推出一款跨平台 SwiftUI App 實際上長什麼樣、為什麼激進的程式碼共用被高估了,以及那三個真的共用了的檔案有什麼共同點。
Return 支援的五個平台,取自 Apple 在 developer.apple.com 上的呈現方式。每一個在 Xcode 裡都是獨立的平台 target,而不是執行期的分支。
重點摘要
- Return:主 target(iOS + iPadOS + macOS)18 個 Swift 檔案、tvOS target 10 個、watchOS target 7 個、Widget 2 個(Live Activities),再加上
Return/Shared/裡真正跨平台的 3 個,總共 40 個。 - 三個共用檔案都靠近持久化層:
MeditationSession、SessionStore、SessionHistoryView。共用的是透過 iCloud 流動的狀態,不是隨平台調整的 UI。 - tvOS 與 watchOS 是獨立的 Xcode target,而不是主 target 裡的
#if os(tvOS)分支。操作模型差太多,塞不進同一個 ContentView。 - 即使在 iOS/iPadOS/macOS 這個主 target 內部,
#if os區塊照樣蔓延:ContentView.swift有 10 處、LiveActivityManager.swift8 處、VideoBackgroundView.swift8 處、AudioManager.swift6 處。 - 老實說:在五個 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
五個平台、三個共用檔案、兩個各自獨立的平台 target 再加一個 Widget target,主 target 裡還有大量條件編譯。共用比例約 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 target 同時引用——不是符號連結,也不是包在 Swift package 裡。Apple 的建置系統很樂意把同一個檔案編進三個二進位檔。
SessionStore.swift 是持久化層:NSUbiquitousKeyValueStore(Apple 的 iCloud 鍵值儲存)的一層薄包裝,負責讀寫 MeditationSession 陣列。這個選擇很關鍵:鍵值儲存的同步讓 Return 不必開通 CloudKit 容器就能有跨裝置的紀錄,代價是整個儲存空間上限只有 1 MB。12 對一份平均每筆幾百位元組的冥想紀錄清單來說,這個上限綽綽有餘。SessionHistoryView.swift 則是把這些紀錄畫出來的 SwiftUI 清單。iPhone、iPad、Mac、Watch 與 TV 這幾個 target 使用它們的方式完全一樣。
這三個檔案的共同點是:它們描述的是狀態,不是互動。 MeditationSession 在任何裝置上都是同一個概念。過往紀錄清單在任何裝置上讀起來也一樣。兩者都不牽涉操作介面、視窗管理、音訊路由決策、焦點引擎或數位錶冠。一個檔案只要開始需要知道自己跑在哪個平台上,就不再適合共用了。
其餘檔案為何沒有共用
以 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對上TVContentView與WatchContentView:導覽模型不同(iPhone 靠推入堆疊、TV 靠焦點、Watch 靠清單)。AudioManager對上TVAudioManager與WatchAudioManager:音訊工作階段類別不同,watchOS 對背景音訊的規則更嚴,tvOS 導向 AirPlay 的方式也不一樣。VideoBackgroundView在主 target 裡有 8 個#if os(iOS)分支(外加一個#elseif os(macOS)搭配),涵蓋不同的影片素材(fire_phone.mp4與fire_mac.mp4)、不同的圖層型別,以及不同的長寬比。4
補充一點:主 Return/ target 確實把 iOS、iPadOS 與 macOS 綁在一起。這三個平台共用的程式碼比不共用的多。SwiftUI 的 NavigationStack 三邊都能用,.glassEffect() 也是。視窗管理的差異真實存在,但在同一個 target 裡還處理得來。獨立 target 那條線,我畫在 tvOS 與 watchOS 上。
tvOS 的情況:焦點引擎為何逼出獨立 target
Apple TV 的導覽是圍繞焦點引擎打造的。5 每個可互動的 UI 元素都會宣告自己可取得焦點;Siri Remote 上的方向操作讓焦點在元素之間移動;按下選取鍵就啟動當前取得焦點的元素。tvOS 上的 SwiftUI 透過 .focusable()、.focusEffect,以及回應 @Environment(\.isFocused) 的自訂 ButtonStyle 來呈現這一切——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。兩種樣式在取得焦點時都會反轉顏色與透明度:未取得焦點的按鈕坐在 .ultraThinMaterial 上,取得焦點的按鈕則填入強調色,並放大尺寸、加深陰影。對這個 App 來說,這個模式在結構上就是 tvOS 專屬的。@Environment(\.isFocused) 在 iOS、iPadOS、macOS、watchOS 與 tvOS 上都可用,13 但只有在 tvOS 上,焦點驅動的導覽才是主要互動模型——Siri Remote 不會產生任何指標或觸控事件。在 iPhone 或 iPad 上,對應的控制項是靠點擊做命中測試;在 Mac 上則是滑過或點按。TVFocusModifier.swift 裡的按鈕樣式假設焦點就是使用者的主要操作線索,整個視覺回饋都繞著它設計。想寫出一個 ContentView 同時處理 iOS 的觸控、Mac 的滑鼠停留與 tvOS 的焦點導覽,沒有什麼好辦法。視圖結構本來就不一樣:tvOS 的 ContentView 是由可取得焦點的列所組成的一張圖,iOS 的 ContentView 則是點一下就動作的堆疊。
時長選擇器也一樣。在 iPhone 上,它從底部滑上來、接受點擊。在 Apple TV 上,它是一排可取得焦點的水平儲存格,使用者用遙控器在其間移動。TVDurationPicker.swift 之所以自成一個檔案,是因為這種以儲存格為基礎的焦點設計,在 iPhone 上根本沒有對應物。硬塞進同一個檔案,只會得到兩套毫不相干的 UI 被 #if os(tvOS) 黏在一起。
watchOS 的情況:延伸執行階段、HealthKit 與更小的介面
watchOS 多了兩個其他平台沒有的結構性限制:
WKExtendedRuntimeSession,用來在錶面變暗時維持 App 的反應能力。8 少了它,watchOS 會在每一秒的跳動之間積極暫停 App,計時就開始飄移。Return 在 watchOS target 的Info.plist裡宣告WKBackgroundModes: mindfulness,讓系統認得這個使用情境並撥給執行時間額度;延伸執行階段本身則是用預設的WKExtendedRuntimeSession()建構式建立的。- 透過
NSUbiquitousKeyValueStore做 iCloud 同步,而不是 WatchConnectivity。7 Return 的紀錄同步搭的是 iPhone、iPad 與 Mac target 共用的那個鍵值儲存,所以在錶上記錄的一次冥想,不需要任何錶對手機的直接訊息傳遞,就會出現在 iPhone 的歷史紀錄畫面裡。WatchConnectivity 未來或許可以拿來做即時狀態同步,但 Return 選了更簡單的模型:每台裝置都寫進同一個 iCloud 鍵值儲存,任何一台裝置下次讀取時看到的就是聯集。
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)。錶端這個類別的篇幅大約是主版本的一半。
主 target(iOS/iPadOS/macOS)內部發生什麼事
即使在主 target 內部,共用也不是理所當然。ContentView.swift 有十個 #if os(macOS) 或 #if !os(macOS) 區塊;LiveActivityManager.swift 八個;VideoBackgroundView.swift 八個;AudioManager.swift 六個。Live Activities 是 iPhone 專屬功能,所以整個 LiveActivityManager 都包在 #if os(iOS) 裡。iPhone 上的時長選擇器與 iPad、Mac 上的版面不同,於是 ContentView 裡就有了並行的版面分支。
行得通的模式是:平台差異小就用 #if os(...)(鍵盤行為不同、間距不同、少了某個 API),結構差異大就開獨立 target(焦點對觸控、體能訓練工作階段對計時器)。 我最後採用的門檻是「分支超過約 10 行」。低於這個數字,條件編譯沒問題;高於它,這個檔案就是同時在做兩件事,而第二件事該搬去另一個 target。
什麼時候不該五個平台全上
說點實話。
如果您的 App 資訊密度高,就跳過 Apple Watch。 46mm 的螢幕塞不下 30 筆清單、一個時長選擇器再加一頁設定。Return 能在 watchOS 上活下來,是因為核心互動只有一顆按鈕(開始/停止計時)。生產力 App、理財 App 或影音密集的 App 做不到。
如果您的 App 互動頻繁,就跳過 Apple TV。 電視適合環境式體驗(房間另一頭螢幕上跑著的計時器、播放中的音樂)。任何需要使用者頻繁輸入的東西,都是在跟平台作對。Return 之所以上 tvOS,是因為「設一個 20 分鐘的計時器,然後看著螢幕上的火」正好是最合適的環境式情境。記事 App 放上去只會很痛苦。
如果您的 App 是手機優先的介面,就跳過 Mac。 SwiftUI 在 Mac 上能跑,但 NavigationStack 的推入模型跟真正的 Mac 側邊欄一比,會顯得像玩具。要是這個 App 在 Mac 上會給人半成品的感覺,那就改出 Catalyst 版(它會轉換 iPad App),或者乾脆先跳過 Mac,等到做得出 Mac 原生 UI 再說。
如果您沒做 size class 適配,就跳過 iPad。 把 iPhone App 拉大填滿 iPad,看起來就是廉價。iPad 至少需要一個帶側邊欄的 NavigationSplitView,理想上要有真正的雙欄版面。Return 在 iPad 上用分割視圖、在 iPhone 上用堆疊。程式碼在同一個 target 裡,但 UI 確實不同。
我畫的規則是:當 App 的核心互動能對應到某個平台的輸入模型時,才在那個平台上推出。冥想計時器適合 Apple Watch(點一下就開始),也適合 Apple TV(設好就不管)。看板工具兩邊都別上。
哪些東西能毫不費力地跨平台
在 Return 裡,真正跨越五個平台共用的有三樣:
- 資料模型(
MeditationSession)。這個結構在每個平台上都一模一樣,透過NSUbiquitousKeyValueStore同步,任何平台都能讀到其他平台寫入的內容。 - 歷史紀錄畫面(
SessionHistoryView)。過往紀錄的List在 iPhone、iPad、Mac、Apple Watch 與 Apple TV 上的呈現完全相同。SwiftUI 的List是少數能乾淨適應這五種外型的基本元件之一。 - 持久化包裝層(
SessionStore)。讀寫與平台無關;底層儲存(NSUbiquitousKeyValueStore)在哪裡都是同一套 API。
三個概念:狀態、清單呈現、持久化。只要是關於狀態與呈現、又不牽涉特定硬體輸入模型的東西,就可以共用。 只要碰到輸入、焦點、音訊路由、螢幕尺寸或背景執行,就不行。
這個模式在《用 AI 代理開發 iOS App》指南裡也出現過,我在那裡用不同的說法講了同一件事:iOS App 中代理能寫的部分,與人類負責的部分共用了大部分程式碼;而需要人類判斷的部分(簽章、視覺打磨、效能),正好也是跨平台最難共用的部分。9 這兩條界線是對齊的,講的都是領域知識從哪裡開始變得重要。
多平台要付出什麼代價
ROI 並不對稱。在 iPhone App 上加 iPad,大概多付出 20% 的程式碼(size class 分支、部分地方的分割視圖)。在同一個 target 上再加 Mac,又多 15% 到 20%(#if os(macOS) 分支、選單列、視窗管理)。對一個小型 App 來說,每多一個主要 target 就多出約 10 個檔案。
Apple Watch 與 Apple TV 才是貴的那兩個。替 Return 加上 watchOS,需要在獨立 target 裡新增 11 個檔案,包含專屬的音訊、計時與 HealthKit 管理器。加上 tvOS,則是在另一個獨立 target 裡新增 10 個檔案,包含焦點管理與自訂的時長選擇器。兩者加起來,讓這個在使用者功能層面上完全相同的 App,Swift 的表面積幾乎翻倍。
決定五個平台全上,並不是「我們想為了多平台而多平台」。那是一連串各自獨立的判斷:上 Apple Watch,是因為冥想計時器本來就該在手腕上;上 Apple TV,是因為環境式的大螢幕適合在房間裡進行的長時間練習;上 Mac,是因為有些使用者會在會議之間於辦公桌前冥想。每個平台都是靠一個真實的使用情境,才掙到自己的 target。
如果某個功能撐不起自己的 target,比較划算的做法是跳過那個平台,把力氣加倍投在這個 App 表現出色的平台上。
這對您的 App 意味著什麼
三個重點。
- 預設每個主要平台群組配一個 target。 iOS + iPadOS + macOS 放在同一個 target 行得通,因為核心互動(觸控加游標)相近。tvOS 獨立一個 target,watchOS 再獨立一個。每個獨立 target 大約多花 10 個檔案,但省下一個
#if分支無限增生的萬能類別。 - 要激進地共用狀態,而不是互動。 Codable 的模型結構、持久化包裝層與
List的呈現幾乎可以免費跨平台。計時管理器、音訊管理器與內容視圖則不行。 - 每個平台都要掙來。 別因為做得到就上 watchOS。等到 App 的核心互動能對應平台的輸入模型時再推出,其餘的就跳過。
這個模式與我為同一系列 App 寫過的另外三個介面層彼此呼應:給 Apple Intelligence 的具型別 App Intents、給跨 LLM 代理的 MCP 伺服器,以及給裝置前那個人的 Liquid Glass。同一疊架構的最外層就是平台:這個 App 究竟會在哪些螢幕上執行。挑平台時,要跟挑 AI 介面層一樣審慎。
常見問題
共用程式碼為什麼不用 Swift package?
我考慮過。就三個檔案而言,Swift package 帶來的繁文縟節多過它省下的功夫。只要勾選 Target Membership,Apple 的 Xcode 26 建置系統就很樂意把同一份原始檔編進多個 target。改用 package,就得多一個 Package.swift、多一個測試 target,而且每次重構都得穿過一層間接。對一個小小的共用核心來說,比較簡單的答案勝出。10
SwiftData 在 watchOS 與 tvOS 上能用嗎?
SwiftData 支援 iOS 17+、macOS 14+、watchOS 10+ 與 tvOS 17+,涵蓋 Return 支援的每一個平台。11 MeditationSession 是單純的 Codable 結構,而不是 @Model,因為 Return 用 NSUbiquitousKeyValueStore 而非 SwiftData 容器來同步歷史紀錄。同樣的模式套用在 @Model 型別上也成立:模型檔案共用,持久化容器則在必要時各平台各自處理。
該用 Mac Catalyst 還是原生的 Mac target?
當 iPad App 本身夠好、好到用 Catalyst 重建出來的 Mac 版讀起來就像原生時,Catalyst 就是對的工具。Return 的主 target 是真正的多平台 target(不是 Catalyst),用 SwiftUI 為 iOS、iPadOS 與 macOS 建置成同一個二進位檔。Mac 版 UI 靠 #if os(macOS) 呈現得與 iPad 不同:用側邊欄取代 sheet、按鈕加上鍵盤快捷鍵等等。Catalyst 會比較省事,但 Mac 版 UI 就會看起來像一個放在 Mac 上的 iPad App——那正是 Catalyst 最惡名昭彰的失敗模式。
小型 App 值得上 Apple TV 嗎?
大概不值得。Apple TV App 的使用情境很特定(環境式、影音、休閒遊戲)。如果您的 App 不屬於其中一種,這個平台的受眾規模撐不起那 10 個 Swift 檔案。Return 之所以特地支援 tvOS,是因為在房間另一頭的螢幕上進行長時間冥想,是少數幾個與生產力沾邊、又剛好適合這個平台的情境之一。
五個平台全部推出要花多少工夫?
很難給出精確數字,得看 App 而定。Return 從第一天就是多平台推出,而不是後來一個一個加,這比事後補強來得快。粗略的經驗法則是:只支援 iPhone 的 MVP 再加上 iPad 與 Mac 支援,大約是純 iPhone 版的 1.5 倍。加上 Apple Watch 再多 0.5 倍,加上 Apple TV 又多 0.5 倍。所以一次推出五個平台的首發版本,大約是純 iPhone 版工作量的 2.5 倍——但要注意,這是一次由代理協助的建置,大部分重複的程式碼是由 Claude Code 批次改寫,而不是手動敲出來的。
參考資料
-
作者的 Return,一款於 2026年4月21日在 App Store 上架的冥想計時器 App。原生 target:iOS 26+、iPadOS 26+、macOS 26+、watchOS 26+、tvOS 26+。全程使用 SwiftUI。跨裝置歷史紀錄採用
NSUbiquitousKeyValueStore。 ↩ -
Apple Developer,“Configuring a Multi-Platform App”,以及 WWDC 2024 的 “SwiftUI essentials” 場次。Apple 的預設建議偏向單一 target 搭配環境驅動的適配;本文採取的多 target 路線是刻意的偏離。 ↩
-
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 的焦點引擎,與 iOS 的觸控或 Mac 的指標是根本不同的導覽模型。 ↩
-
Return/ReturnTV/TVFocusModifier.swift中的上線程式碼。定義了兩個ButtonStyle型別(TVCapsuleButtonStyle與TVCircleButtonStyle),兩者都包住@Environment(\.isFocused),在取得焦點時反轉顏色與透明度,並套用縮放與陰影。 ↩ -
Apple Developer,“WatchConnectivity”。這是用於 iPhone 與已配對 Watch 之間通訊的框架;Return 並未用它來同步紀錄,而是改用 iCloud 鍵值儲存。 ↩
-
Apple Developer,“WKExtendedRuntimeSession”,以及 Info.plist 的 “WKBackgroundModes” 鍵。文件對
mindfulness這個值的說明是:「Enables extended runtime sessions for silent meditation」——正好適合冥想計時器。Return 建立預設的WKExtendedRuntimeSession(),並在 watchOS target 的Info.plist裡宣告WKBackgroundModes: mindfulness。上線程式碼:Return/ReturnWatch Watch App/ReturnWatchApp.swift定義了WatchSessionManager: NSObject, WKExtendedRuntimeSessionDelegate;WatchTimerManager.swift把延伸執行階段的工作委派給它。 ↩ -
作者在 用 AI 代理開發 iOS App 中的分析——這是一份橫跨 8 個上線 App、關於代理協助 iOS 開發的實務指南。 ↩
-
Apple Developer,“Configuring a Multi-Platform App”。Target membership 讓同一份原始檔不必透過 Swift package 就能編進多個 target。對小型共用核心而言,這才是對的工具。 ↩
-
Apple Developer,“SwiftData” 平台支援狀況。支援 iOS 17+、iPadOS 17+、macOS 14+、watchOS 10+、tvOS 17+、visionOS 1+,涵蓋全部五個 Apple 平台家族。 ↩
-
Apple Developer,“NSUbiquitousKeyValueStore”。Apple 的 iCloud 鍵值儲存,用於在使用者的裝置之間同步少量狀態。依 Apple 公布的限制,所有鍵加總的儲存空間上限為 1 MB。上線程式碼:
Return/Return/Shared/SessionStore.swift。 ↩ -
Apple Developer,
EnvironmentValues.isFocused。支援 iOS 14+、iPadOS 14+、macOS 11+、tvOS 14+、watchOS 7+。這個 API 是跨平台的;差別在於焦點是不是使用者的主要導覽線索。 ↩