Visually edit SwiftUI views
Developer Tools 入门 22m

可视化编辑 SwiftUI 视图

Visually edit SwiftUI views

2020年6月24日

在 Apple 官方观看视频

一句话判断

Xcode 12 的 SwiftUI 可视化编辑器终于让”所见即所得”的 UI 开发在 Apple 平台上成为现实——如果你还在用纯代码方式写所有 SwiftUI 界面,你可能错过了大幅提升效率的机会。

这场 Session 讲了什么

Xcode 12 引入了全新的 SwiftUI 可视化编辑能力,让开发者可以在 Canvas 中直接拖拽、调整和预览 SwiftUI 视图。Session 详细介绍了编辑器的三种模式:仅代码、仅 Canvas、以及代码+Canvas 并排显示。Canvas 中的任何视觉修改都会自动反映到代码中,代码的修改也会实时反映到 Canvas 上。

核心功能包括:属性检查器(Inspector)中的可视化控件——你可以直接在侧边栏中调整颜色、字体、间距等属性,而不需要记住 Spacer().padding() 等修饰符的具体参数。Canvas 支持 Drag & Drop 操作——你可以从对象库中拖一个 Button 到 Canvas 上,Xcode 会自动在代码中插入对应的 Button 视图。

Session 还演示了嵌套视图的编辑体验。当你选中 Canvas 中的一个子视图时,Xcode 会高亮对应的代码范围。你可以在 Canvas 中直接插入新的视图修饰符,Xcode 会自动在正确的位置插入代码。整个体验的目标是让 SwiftUI 开发像使用 Interface Builder 一样直观,同时保留代码的完全控制权。

值得深挖的点

Canvas 与代码的双向同步机制

编辑器的核心是双向同步:Canvas 修改 -> 代码更新,代码修改 -> Canvas 刷新。这个同步不是简单的文本替换,而是基于 AST(抽象语法树)的结构化编辑。当你通过 Inspector 修改 .font(.title).font(.headline) 时,Xcode 精确地替换修饰符参数,不会影响周围的代码格式。这种结构化编辑保证了双向同步不会破坏代码的可读性。

Preview vs Editor Canvas 的区别

Preview(PreviewProvider)是编译时确定的可执行预览,运行在真实的 SwiftUI 渲染管线中。而 Editor Canvas 更轻量,它不运行完整的应用代码,而是通过静态分析推断视图的结构。这意味着 Canvas 可以做到近实时的刷新,但某些动态行为(如 @State 变化、网络请求)在 Canvas 中不会生效,需要切换到 Preview 模式。

代码片段

// 在 Xcode Canvas 中可视编辑的典型 SwiftUI 视图
import SwiftUI

struct CoffeeOrderView: View {
    let drinks: [Drink]
    @State private var selectedDrink: Drink?
    
    var body: some View {
        NavigationView {
            List(drinks) { drink in
                HStack {
                    // 图片占位符(Canvas 中可直接拖拽替换)
                    Image(drink.imageName)
                        .resizable()
                        .frame(width: 60, height: 60)
                        .cornerRadius(8)
                    
                    VStack(alignment: .leading, spacing: 4) {
                        Text(drink.name)
                            .font(.headline)
                        Text(\(drink.price)")
                            .font(.subheadline)
                            .foregroundColor(.secondary)
                    }
                    
                    Spacer()
                    
                    if selectedDrink?.id == drink.id {
                        Image(systemName: "checkmark.circle.fill")
                            .foregroundColor(.green)
                    }
                }
                .contentShape(Rectangle())
                .onTapGesture {
                    selectedDrink = drink
                }
            }
            .navigationTitle("选择饮品")
        }
    }
}
// 使用 Inspector 在 Canvas 中调整的属性
// 以下属性都可以通过 Xcode 的 Inspector 面板直接修改
// 无需手动输入代码
struct CustomizableCard: View {
    var title: String = "今日推荐"
    var subtitle: String = "精选咖啡豆"
    @Binding var showDetails: Bool
    
    var body: some View {
        VStack(spacing: 12) {
            Text(title)
                .font(.title2)           // Inspector: 字体选择器
                .fontWeight(.semibold)   // Inspector: 字重选择器
                .foregroundColor(.primary) // Inspector: 颜色选择器
            
            Text(subtitle)
                .font(.subheadline)
                .foregroundColor(.secondary)
            
            Button("查看详情") {
                showDetails.toggle()
            }
            .buttonStyle(.bordered)
            // Inspector 中可以直接切换 button style
        }
        .padding(20)              // Inspector: 间距滑块
        .background(Color(.systemBackground))
        .cornerRadius(12)         // Inspector: 圆角滑块
        .shadow(color: .black.opacity(0.1), radius: 8, x: 0, y: 4)
        // Inspector: 阴影控制
    }
}
// 使用 Canvas 快速原型设计
struct PrototypeView: View {
    @State private var textInput = ""
    @State private var isToggleOn = true
    @State private var sliderValue = 0.5
    
    var body: some View {
        Form {
            Section("输入") {
                TextField("输入内容", text: $textInput)
                // Canvas 中可直接从对象库拖入 TextField
                
                Toggle("启用功能", isOn: $isToggleOn)
                // Canvas 中可切换 Toggle 状态预览
                
                Slider(value: $sliderValue, in: 0...1) {
                    Text("数值: \(String(format: "%.1f", sliderValue))")
                }
            }
            
            Section("预览") {
                Text("你输入了: \(textInput)")
                    .font(.body)
                
                Text("开关状态: \(isToggleOn ? "开" : "关")")
                Text("滑块值: \(String(format: "%.2f", sliderValue))")
            }
        }
    }
}

最佳实践

  • 在 Xcode 偏好设置中开启”Canvas 默认显示”,让每次打开 SwiftUI 文件都自动显示可视化编辑器
  • 对于复杂的布局,先用 Canvas 拖拽出大致结构,再在代码中精调细节
  • 使用 Option + Click 在 Canvas 中查看特定视图的布局边界和尺寸
  • 当 Canvas 显示异常时,用 Editor > Canvas > Refresh 刷新,不要重启 Xcode
  • 给团队成员统一编辑器偏好设置(并排模式 vs 纯代码模式),减少 Git diff 中的无意义变更

还有什么值得关注

  • Xcode 12 的 Minimap 功能可以和 Canvas 配合使用,快速定位大文件中的特定视图
  • Canvas 支持 Accessibility Inspector,可以直接在可视化编辑中检查无障碍标签
  • ViewBuilder 中使用 if/else 分支时,Canvas 会展示当前生效的分支,可以点击切换预览不同分支
WWDC 2020