Xcode 预览新升级:arguments 让界面调试井井有条

序:"案发"

毛利侦探事务所又乱了:同一张「下载卡片」案发现场,有时在等、有时下到 42%、有时 98%、有时成功、有时断网。

柯南盯着 Canvas 叹气------总不能每条线索开一间房、再一间间跑吧?

好消息来了:Xcode 27 的 #Preview(arguments:),像把证据一次性摊在桌上,格子点开就能单独细看。


多数 SwiftUI 视图都不止一种样子:空态、有数据、加载中、失败、权限、边角情况......这些状态本该放一起对照。可过去要么 VStack 全堆一个 Preview,要么写一串几乎雷同的 #Preview------像服部喊「一起看」,实际却在走廊里来回跑。

Xcode 27 引入了 #Preview(arguments:):丢给它一组值,每个参数都会变成同一预览组里可独立选择的变体:

swift 复制代码
@available(iOS 26.0, *)
#Preview(arguments: DownloadItem.previewItems) { item in
    DownloadCard(item: item)
}

Canvas 会把参数排成网格;需要放大镜时,再点开某一个变体单独看。

#Preview(arguments:) 需要 Xcode 27,可用平台:iOS 26、macOS 27、tvOS 26、watchOS 26、visionOS 26。


先给状态建档

例子是一张小下载卡。

状态放在 DownloadItem 里,Preview 只负责塞一个参数------像柯南只递「这一份证物」。

swift 复制代码
enum DownloadState {
    case waiting
    case downloading(progress: Double)
    case completed
    case failed(message: String)
}

struct DownloadItem {
    let fileName: String
    let fileSize: String
    let state: DownloadState
}

struct DownloadCard: View {
    let item: DownloadItem
    // 具体 UI 省略,完整实现见文末示例工程
}

建一份 Preview 证物清单

在视图旁或 Preview 专用文件里扩展即可:

swift 复制代码
extension DownloadItem {
    static let previewItems: [Self] = [
        .init(fileName: "WWDC sessions.zip", fileSize: "2.4 GB", state: .waiting),
        .init(fileName: "WWDC sessions.zip", fileSize: "2.4 GB", state: .downloading(progress: 0.42)),
        .init(fileName: "WWDC sessions.zip", fileSize: "2.4 GB", state: .downloading(progress: 0.98)),
        .init(fileName: "WWDC sessions.zip", fileSize: "2.4 GB", state: .completed),
        .init(fileName: "WWDC sessions.zip", fileSize: "2.4 GB", state: .failed(message: "The connection was lost.")),
    ]
}

老办法一:一状态一 #Preview

最直接------每个状态单独声明:

swift 复制代码
#Preview("Waiting", traits: .sizeThatFitsLayout) {
    DownloadCard(item: DownloadItem.previewItems[0])
}
// Downloading 42% / 98% / Completed / Failed ... 同理

各自独立,但样板代码重复。

Canvas 当成多个 Preview,你只能来回切换,很难「一眼对照」。加状态还得加声明,名字和数据还得自己对齐------小兰都懒得收拾这种案卷。


老办法二:全塞进一个 VStack

想一次看完,可以:

swift 复制代码
#Preview("All download states", traits: .sizeThatFitsLayout) {
    VStack(spacing: 12) {
        ForEach(DownloadItem.previewItems.indices, id: \.self) { index in
            DownloadCard(item: DownloadItem.previewItems[index])
        }
    }
    .padding()
    .background(Color(uiColor: .systemGroupedBackground))
}

对比很快,但 Canvas 仍只认一个 Preview。

卡片只是大布局里的子视图,不是独立变体------想单独打开某一态、或把网格当状态清单,都不顺手。像把所有证据叠成一摞:看得见,却难抽。


新招:#Preview(arguments:) 生成变体

新宏吃一个数组,闭包接收单个元素:

swift 复制代码
@available(iOS 26.0, *)
#Preview(
    "Download states",
    traits: .sizeThatFitsLayout,
    arguments: DownloadItem.previewItems
) { item in
    DownloadCard(item: item)
        .padding()
        .background(Color(uiColor: .systemGroupedBackground))
}

该方法接受泛型类型 [T] :枚举、模型、专用场景类型都行;不必 Identifiable / Hashable / Equatable / CaseIterable

第一个字符串是预览组名。Canvas 渲染含五个参数的网格 ,顺序跟数组一致。若不自定义标签,Xcode 会用默认 description,往往又长又难扫------

灰原大概会说:「标签不清晰,推理效率为零。」


给每个 Canvas 变体起名

让参数类型遵循 CustomStringConvertible,用 description 返回短标签:

swift 复制代码
extension DownloadItem: CustomStringConvertible {
    var description: String {
        switch state {
        case .waiting: "Waiting"
        case let .downloading(progress):
            "Downloading \(progress.formatted(.percent.precision(.fractionLength(0))))"
        case .completed: "Completed"
        case .failed: "Failed"
        }
    }
}

两个都叫 Downloading 会分不清;标签要短、稳、组内唯一

点网格里某一格,Xcode 会单独打开该变体,方便细看;有控件时还可进 Interactive 模式------真正的「单独审讯室」。


参数留给状态,环境留给 traits

arguments 适合有限且有意义的输入状态轴:主状态 + 少量会暴露布局问题的边角(超长文件名、大数值、本地化错误文案)。

配色、动态字体、语言、设备、方向,请用 preview traits 或另开预览组。否则小清单会膨胀成难扫的大矩阵------比黑衣组织成员表还可怕。

参数只描述变体的初始输入,不替代本地可变状态;交互要改 binding / 可观察模型时,用 @Previewable 或小包装视图。


部署目标仍可留在 iOS 17

App 不必升到 iOS 26:保持原部署版本,只给新 Preview 标 @available(iOS 26.0, *)

swift 复制代码
@available(iOS 17.0, macOS 14.0, tvOS 17.0, visionOS 1.0, watchOS 10.0, *)
nonisolated struct $s23PreviewArgumentsExample0023DownloadCardswift_elFCffMX145_0_33_ABA51F1A69BAC7119FC7A1DC29AA9D8ELl0A0fMf_15PreviewRegistryfMu_: DeveloperToolsSupport.PreviewRegistry {
    static var fileID: String {
        "PreviewArgumentsExample/DownloadCard.swift"
    }
    static var line: Int {
        146
    }
    static var column: Int {
        1
    }
 
    @MainActor static func makePreview() throws -> DeveloperToolsSupport.Preview {
        if #available(iOS 26.0, *) {
            DeveloperToolsSupport.Preview(
                "Download states",
                traits: .sizeThatFitsLayout,
                arguments: DownloadItem.previewItems
            ) { item in
                DownloadCard(item: item)
                    .padding()
                    .background(Color(uiColor: .systemGroupedBackground))
            }
        } else {
            throw DeveloperToolsSupport.PreviewUnavailable()
        }
    }
}

在声明上 Control-click → Expand Macro 可见:注册表基于更旧的 Preview 基础设施(自 iOS 17),新重载在运行时用 if #available(iOS 26.0, *) 守护;不够新就抛 PreviewUnavailable()。外层的 @available(iOS 17.0, ...) 不会arguments: 在 iOS 17--25 真可用------它只是让低部署目标能干净编译。


"结案"

同一张下载卡、五种案情,不必再像小五郎一样一间间踢门。

把状态收成清单,交给 #Preview(arguments:)------

柯南式结案:证据摊开,一眼看清;要点哪条,点哪条。

那么,宝子们学会了吗?

感谢大家的观赏,我们下次不见不散!

相关推荐
大熊猫侯佩1 天前
WWDC26:把 SwiftUI Toolbar 真正握在手心里
swiftui·swift·apple
大熊猫侯佩1 天前
WWDC 26 全新 ResultsObserver:终于能在 View 外面盯着 SwiftData 了
swiftui·swift·apple
大熊猫侯佩1 天前
别再把大模型供在云端了,WWDC26 CoreAI 大模型下凡实战
ai编程·swift·wwdc
00后程序员张1 天前
有没有更轻量的 iOS 开发环境?快蝎kxapp轻量化路径与构成
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
sakiko_2 天前
Swift学习笔记39-实战注意事项
前端·笔记·学习·swift
User_芊芊君子2 天前
STT + LLM + TTS:用一条流水线搓了个中英双向翻译助手
ide·macos·xcode
东坡肘子2 天前
Apple Intelligence 已通过审核,即将在中国提供服务 -- 肘子的 Swift 周报 #148
人工智能·swiftui·swift
初级代码游戏4 天前
iOS开发 Swift 速记7:结构体和类
开发语言·ios·swift
星辰即远方5 天前
计算器仿写总结
学习·ui·ios·cocoa·xcode