Highlight
Xcode 14 允许单个 app target 同时支持 iPhone、iPad、Mac 和 Apple TV 等多个 destination,通过共享代码和默认设置减少重复工作,同时保留按平台条件化编译和配置的能力。
核心内容
你开发了一个 iOS 应用,运行得很好。现在你想把它带到 Mac 上。
在 Xcode 14 之前,这意味着创建两个独立的 target,维护两份代码,管理两套构建设置。代码改动需要在两边同步,签名证书和描述文件也要分别配置。
(01:29)Xcode 14 改变了这个局面。单个 app target 可以声明支持多个 destination,代码和设置默认共享,只在需要时按平台定制。
什么时候用多平台 target
(01:02)如果你的项目在不同平台上有显著不同的代码库,或者各平台依赖完全不同的底层技术,继续使用独立的 target 更合适。
如果应用使用共同的代码库,设置大部分可以共享,多平台 target 是更好的选择。SwiftUI 应用尤其适合这种模式,因为可以访问每个平台的完整 SDK。
添加 Mac destination
(02:37)Session 以一个 Food Truck 应用为例。这个应用已经在 iPhone 和 iPad 上运行良好,现在要添加原生 Mac 支持。
在 target 的 General 标签页中,可以看到当前支持的 destination 列表。这个应用已经有 “Designed for iPad”,让 Apple silicon Mac 可以运行未修改的 iOS 应用。但要获得真正的 Mac 体验,需要添加 “Mac” destination。
(03:26)Mac 有三种选择:
- Mac:原生 macOS 应用,完整 macOS SDK,适合 SwiftUI 项目
- Mac Catalyst:将 iPad 应用转换为 Mac 应用,适合大量使用 UIKit 的项目
- Designed for iPad:Apple silicon Mac 直接运行 iOS 应用,无需修改
Food Truck 使用 SwiftUI,所以选择原生 Mac。
(04:19)添加后 Xcode 会自动更新 target,只保留 Mac 支持的依赖和框架。Xcode 不会修改代码,所以如果有 Mac 不可用的 API,需要手动处理。
条件化构建设置
(05:45)Xcode 14 的 target 编辑器支持按条件设置值。比如 display name 可以按 build configuration 和 SDK 分别设置。
点击设置值旁边的条件化按钮,可以为 Debug、Release、Beta 等配置分别指定值,也可以按 SDK(iOS/macOS)进一步细分。
签名和 Capability
(06:42)自动签名开启时,添加 Mac destination 后会自动生成对应的证书和描述文件。iOS 和 macOS 应用默认使用相同的 bundle identifier,发布到 App Store 时自动实现 Universal Purchase。
Capability 也会自动处理。iOS 上已启用的 capability 如果在 macOS 上可用,会自动应用到 Mac 版本,合并到单个 entitlements 文件中。
处理平台差异
(07:41)切换到 “My Mac” destination 后构建,会发现一些平台相关的问题。
ARKit 在 Mac 上不可用,需要条件化导入:
#if canImport(ARKit)
import ARKit
#endif
(09:15)也可以直接在 Build Phases 中指定某个文件只在特定 SDK 上编译。对于大量使用了平台专属 API 的文件,这种方式更干净。
EditMode 是 iOS 专属的概念,Mac 上用户可以直接选择和编辑内容。需要把相关的环境属性、onChange 修饰符和 EditButton 视图都用 #if os(iOS) 包裹起来。
适配平台体验
(10:59)应用能在 Mac 上编译运行只是第一步。还需要考虑平台差异带来的体验问题。
Food Truck 的网格视图中,甜甜圈缩略图在 Mac 上显得太大。这是因为它们按触摸交互设计的尺寸,而 Mac 有精确的指针设备。
(11:48)把常量改为计算属性,按平台返回不同值:
var thumbnailSize: Double {
#if os(iOS)
return 120
#else
return 80
#endif
}
80 像素的尺寸在 Mac 上看起来更自然。
(12:21)除了裁剪 iOS 专属功能,还可以利用 macOS 的专属能力。SwiftUI 新增了 MenuBarExtra,可以把应用内容放到菜单栏:
#if os(macOS)
MenuBarExtra {
MiniTruckView(model: model)
} label: {
Label("Food Truck", systemImage: "box.truck")
}
.menuBarExtraStyle(.window)
#endif
Mac 用户现在可以从菜单栏快速查看今日信息。
归档和发布
(14:02)单个 target 不意味着单个产品。需要为每个平台分别归档,然后分别上传到 App Store Connect。
在 Xcode Cloud 工作流中,可以为 iOS 和 macOS 分别添加 build、test、archive 动作,还可以配置自动上传到 App Store Connect 并分发给内部 TestFlight 测试团队。
详细内容
条件化编译
(08:48)Swift 提供了多种条件化编译的方式:
// 按模块可用性条件化导入
#if canImport(ARKit)
import ARKit
#endif
// 按操作系统条件化代码
#if os(iOS)
@Environment(\.editMode) private var editMode
#endif
// 条件化视图修饰符
#if os(iOS)
.onChange(of: editMode?.wrappedValue) { newValue in
if newValue?.isEditing == false {
selection.removeAll()
}
}
#endif
// 条件化视图
#if os(iOS)
EditButton()
#endif
关键点:
canImport适合处理框架级别的可用性,不需要维护平台列表os(iOS)/os(macOS)适合处理 SDK 级别的差异- 条件化编译在编译期处理,不会产生运行时开销
- 条件化代码块必须保持语法完整,不能拆开跨越条件化边界
文件级别的平台限制
(09:17)对于大量使用了平台专属 API 的文件,可以在 Build Phases 中设置编译条件:
- 打开 target > Build Phases > Compile Sources
- 找到目标文件
- 在右侧的条件列中指定
"$(SDKROOT)" == "iphoneos*"或类似条件
这样整个文件只在匹配的 SDK 上编译,避免文件中到处都是 #if。
平台适配原则
(13:09)SwiftUI 把平台预期直接 baked 进 API。很多界面元素会自动获得适合各平台的外观。
这意味着:
- 尽量使用 SwiftUI 的原生控件,让它们自动适配各平台
- 过度自定义控件会失去这种自动适配
- 为触摸设计的尺寸在桌面平台上通常过大
- 考虑每个平台的交互范式差异(触摸 vs 指针、手势 vs 菜单)
核心启发
-
做什么:把现有的 iOS SwiftUI 应用扩展到 Mac
-
为什么值得做:Xcode 14 的多平台 target 让维护成本大幅降低,同一套代码服务更多用户
-
怎么开始:在 target 的 General 标签页中添加 Mac destination,处理构建错误,逐步适配 Mac 交互习惯
-
做什么:为 Mac 版本添加菜单栏快捷入口
-
为什么值得做:MenuBarExtra 是 macOS 独有的能力,让应用随时可用而不占用 Dock
-
怎么开始:在 App 结构体中用
#if os(macOS)包裹 MenuBarExtra Scene -
做什么:构建一个跨平台的效率工具,利用各平台特长
-
为什么值得做:iOS 版本负责移动场景的数据采集,Mac 版本负责数据分析和批量处理
-
怎么开始:用 SwiftUI 构建共享的 UI 层,用
#if os()分离平台专属功能,通过 App Group 共享数据 -
做什么:配置 Xcode Cloud 自动构建和分发多平台应用
-
为什么值得做:一次提交自动构建 iOS 和 macOS 版本,直接推送到 TestFlight
-
怎么开始:在 Xcode Cloud 工作流中添加多个 archive 动作,分别指定 iOS 和 macOS 的 destination
关联 Session
- What’s new in Xcode — Xcode 14 新功能总览
- Bring your iOS app to the Mac — 将 iOS 应用带到 Mac 的多种方式
- Get the most out of Xcode Cloud — Xcode Cloud 工作流配置
评论
GitHub Issues · utterances