← 所有文章

五个 Apple 平台,三个共享文件:Return 是如何真正做到跨平台 SwiftUI 的

我的冥想计时应用 Return 运行在五个 Apple 平台上:iPhone、iPad、Mac、Apple Watch 和 Apple TV。1 代码库共有 40 个 Swift 文件(不含测试)。其中只有 3 个被五个平台共享。 其余部分拆进各自独立的 Xcode target,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 里都是独立的平台 target,而不是运行时的一条分支。

一句话总结

  • Return:主 target 18 个 Swift 文件(iOS + iPadOS + macOS),tvOS target 10 个,watchOS target 7 个,widget 2 个(实时活动),以及 Return/Shared/ 里 3 个真正跨平台的文件。合计 40 个。
  • 这 3 个共享文件都跟持久化有关:MeditationSessionSessionStoreSessionHistoryView。它们承载的是通过 iCloud 流动的状态,而不是需要适配平台的 UI。
  • tvOS 和 watchOS 是独立的 Xcode target,而不是主 target 里的 #if os(tvOS) 分支。这两者的操控模型差异太大,塞不进同一个 ContentView。
  • 即便在 iOS/iPadOS/macOS 这一个主 target 内部,#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

五个平台,三个共享文件,两个独立的平台专属 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),好让系统在息屏期间仍保持应用响应,输入也走数码表冠而非触摸。三个平台,三套差异极深的计时行为。

当然可以把它们统一成 class TimerManager { #if os(watchOS) ... #elif os(tvOS) ... }。结果就是一个有三种模式的类,每种模式四十行被 #if 圈起来的代码,动一下 iOS 那条路径就有可能弄坏 watchOS 那条。这是维护上的噩梦。

三个独立的类、三个文件名,占的磁盘更多,占的脑子更少。看得懂的重复,胜过看不懂的抽象。

同样的逻辑也适用于:

  • ContentViewTVContentViewWatchContentView:导航模型不同(iPhone 是压栈式,TV 是焦点式,Watch 是列表式)。
  • AudioManagerTVAudioManagerWatchAudioManager:音频会话类别不同,watchOS 的后台音频规则更严,tvOS 到 AirPlay 的路由方式也不一样。
  • VideoBackgroundView 在主 target 里有 8 处 #if os(iOS) 分支(外加一个 #elseif os(macOS) 配套分支),覆盖不同的视频素材(fire_phone.mp4fire_mac.mp4)、不同的图层类型和不同的宽高比。4

有一点要说明:主 Return/ target 确实把 iOS、iPadOS 和 macOS 捆在了一起。这三个平台之间共享的代码多于不共享的。SwiftUI 的 NavigationStack 三个平台都能用,.glassEffect() 也是。窗口管理上的差异真实存在,但在一个 target 内部完全处理得了。tvOS 和 watchOS 才是我划下独立 target 这条线的地方。

tvOS 这一例:焦点引擎为什么逼出一个独立 target

Apple TV 的导航是围绕焦点引擎建立的。5 每一个用户可交互的 UI 元素都要声明自己可获得焦点;Siri Remote 上的方向操作让焦点在元素之间移动;按下选择键则激活当前获得焦点的元素。tvOS 上的 SwiftUI 通过 .focusable().focusEffect 以及响应 @Environment(\.isFocused) 的自定义 ButtonStyle 类型把这套机制暴露出来——Apple 自家应用那种视差倾斜效果就是这么做的。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 上,获得焦点后填充强调色,并加大缩放与阴影。就这个应用而言,这套模式在结构上是 tvOS 专属的。@Environment(\.isFocused) 在 iOS、iPadOS、macOS、watchOS 和 tvOS 上都可用,13 但只有在 tvOS 上,焦点驱动的导航才是首要的交互模型——Siri Remote 既不产生指针事件,也不产生触摸事件。在 iPhone 或 iPad 上,同类控件靠点击命中测试;在 Mac 上则是悬停或点按。TVFocusModifier.swift 里的按钮样式默认焦点就是用户的主要可供性,并围绕它设计了全部视觉反馈。想在一个地方写出一个既处理 iOS 触摸、又处理 Mac 悬停、还处理 tvOS 焦点导航的 ContentView,没有什么好办法。视图结构本身就不一样:tvOS 的 ContentView 是一张由可获焦点的行构成的图,iOS 的 ContentView 则是一摞点击即执行的堆栈。

时长选择器也是同理。在 iPhone 上,它从底部滑出,接受点击。在 Apple TV 上,它是一横排可获焦点的单元格,用户用遥控器在其中移动。TVDurationPicker.swift 之所以自成一个文件,是因为这种基于单元格的焦点设计在 iPhone 上根本没有对应物。硬把两者塞进一个文件,无非是用 #if os(tvOS) 把两套毫不相干的 UI 粘在一起。

watchOS 这一例:扩展执行环境会话、HealthKit,以及更小的表面

watchOS 多出两条其他平台没有的结构性约束:

  1. WKExtendedRuntimeSession,用来在手表息屏期间保持应用响应。8 没有它,watchOS 会在每一秒的滴答之间激进地挂起应用,计时随之漂移。Return 在 watchOS target 的 Info.plist 里声明了 WKBackgroundModes: mindfulness,好让系统识别这一使用场景并授予相应的运行预算;会话本身则用默认的 WKExtendedRuntimeSession() 初始化方法创建。
  2. 通过 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 应用在前台无需显式的执行环境会话就能保持响应。用 #if os(watchOS) 把手表逻辑塞进 iOS 的 TimerManager,只会让 iOS 那条路径导入自己永远用不到的 WatchKit 符号,而 watchOS 那条路径又需要 iOS 路径不需要的初始化流程。

WatchHealthKitManager.swift 是主 HealthKitManager 的一个精简版本。记录正念分钟数的方式相同,但授权提示的 UX 不同(手表上无法展示 HealthKitPermissionSheet)。手表版这个类的体量大约是主版本的一半。

iOS/iPadOS/macOS 主 target 内部发生了什么

即便在主 target 之内,共享也不是自动发生的。ContentView.swift 里有十处 #if os(macOS)#if !os(macOS) 块;LiveActivityManager.swift 有八处;VideoBackgroundView.swift 有八处;AudioManager.swift 有六处。实时活动是 iPhone 独有的功能,所以整个 LiveActivityManager 都包在 #if os(iOS) 里。iPhone 上的时长选择器与 iPad、Mac 上的布局不同,于是 ContentView 里存在并行的布局分支。

行之有效的规律是:小的平台差异用 #if os(...)(键盘行为不同、间距不同、缺少某个 API),大的结构性差异用独立 target(焦点对触摸、锻炼会话对计时器)。 我最终采用的阈值是“分支超过约 10 行”。低于这个量,条件编译没问题;高于这个量,这个文件就是在同时干两件事,而第二件事属于另一个 target。

什么时候不该五个平台全上

说点实话。

如果你的应用信息密度高,就跳过 Apple Watch。 46mm 的屏幕装不下一个 30 项的列表、一个时长选择器再加一个设置页。Return 能在 watchOS 上活下来,是因为它的核心交互只有一个按钮(开始/停止计时)。生产力应用、理财应用或富媒体应用做不到。

如果你的应用交互频繁,就跳过 Apple TV。 电视适合的是环境式体验(计时器在房间另一头的屏幕上跑着、音乐播放)。任何需要用户频繁输入的东西都是在跟这个平台对着干。Return 上 tvOS,是因为“设一个 20 分钟的计时器,然后看着屏幕上的火焰”恰恰是最贴切的环境式场景。一个笔记应用在这儿会很难受。

如果你的应用是手机优先的界面,就跳过 Mac。 SwiftUI 在 Mac 上能跑,但 NavigationStack 那套压栈模型与真正的 Mac 侧边栏一比,就显得像个玩具。如果应用在 Mac 上会显得半成品,那就上 Catalyst(它把 iPad 应用转过来),或者干脆先不做 Mac,等你能做出原生 Mac UI 再说。

如果你还没做尺寸类适配,就跳过 iPad。 一个 iPhone 应用被拉伸铺满 iPad,读起来就是廉价。iPad 至少需要一个带侧边栏的 NavigationSplitView,理想情况下是真正的双栏布局。Return 在 iPad 上用分栏视图,在 iPhone 上用堆栈。代码在同一个 target 里,但 UI 是实打实不同的。

我给自己划的规矩是:当应用的核心交互与某个平台的输入模型对得上时,才上那个平台。冥想计时器上 Apple Watch(一按即开)。冥想计时器上 Apple TV(设好就不管)。看板工具两个都别上。

什么东西能不费力地跨过去

在 Return 里,真正做到五个平台共享的只有三样:

  1. 数据模型MeditationSession)。这个 struct 在每个平台上都完全一致,经 NSUbiquitousKeyValueStore 同步,任何平台都能读到其他平台写下的内容。
  2. 历史记录视图SessionHistoryView)。一个展示过往记录的 List,在 iPhone、iPad、Mac、Apple Watch 和 Apple TV 上渲染效果一致。List 是 SwiftUI 中少数能干净地适配全部五种形态因子的原语之一。
  3. 持久化封装SessionStore)。读写与平台无关;底层存储(NSUbiquitousKeyValueStore)在哪儿都是同一套 API。

三个概念。状态、列表渲染、持久化。凡是有状态且偏展示、又不牵涉硬件专属输入模型的东西,都可以共享。 凡是碰到输入、焦点、音频路由、屏幕尺寸或后台执行的,都不行。

这个规律在《用 AI 智能体开发 iOS 应用》指南里也出现过,我在那里用另一套说法讲了同一件事:iOS 应用中智能体能写的部分,与人来写的部分共享着大部分代码;而需要人的判断力的那些部分(签名、视觉打磨、性能)恰恰也是跨平台最难共享的部分。9 这两条边界是重合的。它们讲的都是:领域知识从哪里开始变得要紧。

多平台的代价

这笔 ROI 是不对称的。给一个 iPhone 应用加上 iPad,大概多出 20% 的代码(尺寸类分支、某些地方的分栏视图)。在同一个 target 上再加 Mac,又多出 15–20%(#if os(macOS) 分支、菜单栏、窗口管理)。对小型应用来说,每加一个主要 target 大约多出 10 个文件。

Apple Watch 和 Apple TV 才是贵的那两个。给 Return 加上 watchOS,需要在一个独立 target 里新增 11 个文件,其中包括专属的音频、计时和 HealthKit 管理器。加上 tvOS,则需要在另一个独立 target 里新增 10 个文件,包括焦点管理和一个自定义时长选择器。两者加起来,几乎让这个应用的 Swift 代码面积翻了一倍——而在用户功能层面,它还是同一个应用。

选择五个平台全上,动机并不是“我们就是想做多平台”。那是一连串各自独立的判断:上 Apple Watch,因为冥想计时器本就属于手腕;上 Apple TV,因为环境式的屏幕形态适合长时间的冥想;上 Mac,因为有些用户会在会议间隙于工位上冥想。每个平台都是靠一个真实场景挣来自己的 target 的。

如果一个功能挣不来自己的 target,更划算的做法是跳过这个平台,把力气加倍投在应用真正出色的那些平台上。

这对你的应用意味着什么

三点结论。

  1. 默认按主要平台族划分 target。 iOS + iPadOS + macOS 放一个 target 是可行的,因为核心交互(触摸 + 光标)相近。tvOS 单独一个 target。watchOS 单独一个 target。每个独立 target 大约要多花 10 个文件,但能省掉一个 #if 分支无限膨胀的上帝类。
  2. 积极共享状态,别共享交互。 Codable 的模型 struct、持久化封装以及 List 渲染几乎可以零成本迁移。计时管理器、音频管理器、内容视图不行。
  3. 让每个平台自己挣来资格。 别因为“能做”就上 watchOS。当应用的核心交互与该平台的输入模型对得上时再上,其余的跳过。

这套模式与我为同一族应用写过的另外三个层面彼此配合:面向 Apple Intelligence 的类型化 App Intents,面向跨 LLM 智能体的 MCP 服务器,以及面向设备前那个人的 Liquid Glass。同一套技术栈的最外层就是平台本身:这个应用究竟会跑在哪些屏幕上。选平台,要像选 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 这个 struct 只是普通的 Codable,不是 @Model,因为 Return 的历史记录同步用的是 NSUbiquitousKeyValueStore 而非 SwiftData 容器。对 @Model 类型来说,这套模式同样成立:模型文件共享,持久化容器则在必要时按平台各行其是。

我该用 Mac Catalyst 还是原生 Mac target?

当 iPad 应用本身足够好、好到 Catalyst 重建出的 Mac 版读起来就像原生时,Catalyst 就是对的工具。Return 的主 target 是真正的多平台 target(不是 Catalyst),用 SwiftUI 把 iOS、iPadOS 和 macOS 构建进同一个二进制。Mac 上的 UI 通过 #if os(macOS) 渲染出与 iPad 不同的样子:用侧边栏而不是 sheet,按钮带快捷键,等等。用 Catalyst 会更省事,但那样 Mac UI 就会像一个跑在 Mac 上的 iPad 应用——而这正是 Catalyst 最出名的那种失败方式。

小应用值得上 Apple TV 吗?

多半不值得。Apple TV 应用的适用场景非常具体(环境式、媒体、休闲游戏)。如果你的应用不属于其中之一,这个平台的受众规模撑不起每个应用 10 个 Swift 文件的投入。Return 之所以专门面向 tvOS,是因为“在房间另一头的屏幕上进行长时间冥想”正是少数与生产力沾边、又契合这个平台的场景之一。

五个平台全上要花多长时间?

很难给出精确数字,这取决于应用本身。Return 从第一天就是多平台交付,而不是后来一个个平台补上去——前者比改造要省力得多。粗略的经验法则是:一个只做 iPhone 的 MVP 再加上 iPad 支持和 Mac 支持,大约是纯 iPhone 工作量的 1.5 倍。加上 Apple Watch,再多 0.5 倍。加上 Apple TV,再多 0.5 倍。所以首发就覆盖五个平台,大致是纯 iPhone 投入的 2.5 倍——但要说明的是,这是一次由智能体协助的构建,那些重复代码大多由 Claude Code 批量编辑生成,而非手工敲出来的。

参考资料


  1. 作者的 Return,一款冥想计时应用,2026 年 4 月 21 日上架 App Store。原生 target: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 的默认建议倾向于单一 target 加环境驱动的适配;本文所走的多 target 路线是一次刻意的背离。 

  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) 分支加 1 处 #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 的焦点引擎与 iOS 的触摸或 Mac 的指针在导航模型上有根本区别。 

  6. 生产代码见 Return/ReturnTV/TVFocusModifier.swift。它定义了两个 ButtonStyle 类型(TVCapsuleButtonStyleTVCircleButtonStyle),二者都借助 @Environment(\.isFocused) 在获得焦点时反转颜色与透明度,并施加缩放与阴影。 

  7. Apple Developer,“WatchConnectivity”。这是用于 iPhone 与配对 Watch 之间通信的框架;Return 的记录同步并未使用它,而是依赖 iCloud 键值存储。 

  8. 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, WKExtendedRuntimeSessionDelegateWatchTimerManager.swift 把扩展执行环境相关的工作委托给它。 

  9. 作者的分析见用 AI 智能体开发 iOS 应用,一份基于 8 款生产应用、面向从业者的智能体协助 iOS 开发指南。 

  10. Apple Developer,“Configuring a Multi-Platform App”。Target membership 让一个源文件无需 Swift package 就能编译进多个 target。对小型共享内核来说是对的工具。 

  11. Apple Developer,“SwiftData” 平台可用性。支持 iOS 17+、iPadOS 17+、macOS 14+、watchOS 10+、tvOS 17+、visionOS 1+,覆盖全部五个 Apple 平台族。 

  12. Apple Developer,“NSUbiquitousKeyValueStore”。Apple 的 iCloud 键值存储,用于在用户的多台设备之间同步少量状态。按 Apple 公布的限制,所有键加起来的存储总量上限为 1 MB。生产代码:Return/Return/Shared/SessionStore.swift。 

  13. Apple Developer,EnvironmentValues.isFocused。支持 iOS 14+、iPadOS 14+、macOS 11+、tvOS 14+、watchOS 7+。这个 API 是跨平台的;有区别的是焦点是否为用户的主要导航可供性。 

相关文章

Apple平台矩阵:哪些目标平台值得发布哪些应用

iOS、iPad、Mac、Watch、Vision、TV。六个平台,六份责任。选择Apple目标平台首先是产品决策,其次才是工程决策。

15 分钟阅读

iOS 26 上的 HealthKit + SwiftUI:来自两款已上架应用的授权流程、样本类型与跨平台模式

来自 Water(饮水追踪,HKQuantitySample)和 Return(正念会话,HKCategorySample)的真实生产模式。权限 UX、async 封装、watchOS 变体,以及需要避开的陷阱。

12 分钟阅读

安装与更新 Codex CLI:Mac、Linux、Windows

安装、更新、锁定版本与卸载 OpenAI Codex CLI 的每一种方式:安装脚本、npm、Homebrew、winget,覆盖 macOS、Linux、WSL 与 Windows。

9 分钟阅读