WWDC Quick Look 💓 By SwiftGGTeam
Build a SwiftUI app with the new design

Build a SwiftUI app with the new design

观看原视频

Highlight

Franck 用 Landmarks 示例项目演示如何在 Xcode 26 SDK 下重建一个 SwiftUI app,覆盖 App 结构、Toolbars、Search、Controls 与自定义 Liquid Glass 五个方面的变更。

核心内容

每年发新 SDK,开发者最怕的是「看起来变好看了,但代码要从头改一遍」。这次 iOS 26 / macOS Tahoe 引入 Liquid Glass,第一反应也容易这样:sidebar 变玻璃了、tab bar 浮起来了、按钮变成胶囊形了——是不是要把现有的 toolbar、sheet、button 全部重写?

Franck 给的答案是:先用 Xcode 26 重新编译一次,看看哪些是免费拿到的,再决定要不要写代码。他用一个真实的 Landmarks 示例 app 走了一遍——hero 图片被 sidebar 裁掉了,toolbar 上 5 个 button 视觉上分不清主次,搜索框在 iPhone 和 iPad 上摆放位置不一致。这些问题在旧 API 下要靠手写适配代码解决,而新 SDK 把它们变成一两行 modifier:backgroundExtensionEffect 让图片自动延伸出 safe area、ToolbarSpacer 把 toolbar item 按语义分组、searchable 放在 NavigationSplitView 上自动适配 iPhone 底部和 iPad 顶部 trailing。整段演示的逻辑很清晰:自动获得的改进先吃掉,剩下需要表达的语义才动手写新 API。

详细内容

App 结构:sidebar 默认是 Liquid Glass,浮在内容之上。如果 sidebar 把 hero 图片裁掉,用 backgroundExtensionEffect 让 image 镜像延伸到 safe area 外(03:59)。

// Extend background images

struct LandmarkDetailView: View {
    let landmark: Landmark

    var body: some View {
        ScrollView {
            VStack {
                Image(landmark.backgroundImageName)
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .backgroundExtensionEffect()
            }
        }
    }
}

关键点:

  • .backgroundExtensionEffect() 让 image 在 safe area 外做镜像 + 模糊,原始内容仍在 safe area 内可见。
  • 不需要手动算 sidebar 宽度或重新裁切图片,sidebar 隐藏后能看到镜像区域被自动拓展。

TabView 的 tab bar 在 iPhone 上变成悬浮式,可配置滚动时收起,并能在上方放一个 accessory(如 Music 的播放条)(05:39)。

// Tab bar accessory

TabView {
    // tabs
}
.tabBarMinimizeBehavior(.onScrollDown)
.tabViewBottomAccessory {
    MusicPlaybackView()
}

struct MusicPlaybackView: View {
    @Environment(\.tabViewBottomAccessoryPlacement)
    var placement

    var body: some View {
        if placement == .inline {
            // compact layout
        } else {
            // full layout
        }
    }
}

关键点:

  • tabBarMinimizeBehavior(.onScrollDown) 控制 tab bar 在向下滚动时收起,向上滚动时恢复。
  • tabViewBottomAccessory 在 tab bar 上方放一个常驻 view,借助 bar 收起时空出的位置。
  • 通过环境值 tabViewBottomAccessoryPlacement 区分 .inline(收起后内嵌)与展开两种布局。

Toolbars:item 自动分组,用 ToolbarSpacer 显式控制语义边界(08:26)。

// Visually separate toolbar items

struct LandmarkDetailView: View {
    var body: some View {
        ScrollView {
            ScrollContent()
        }
        .toolbar {
            ToolbarItem { ShareLink() }
            ToolbarSpacer(.fixed)
            ToolbarItem { FavoriteButton() }
            ToolbarItem { CollectionsButton() }
            ToolbarSpacer(.fixed)
            ToolbarItem { InspectorToggle() }
        }
    }
}

关键点:

  • 默认相邻的 ToolbarItem 会被合并到同一个 glass 背景组里。
  • ToolbarSpacer(.fixed) 强制断开背景组,让 favorite / collections 单独一组、share 与 inspector 各自独立。
  • 还可用 ToolbarSpacer(.flexible, placement: .bottomBar) 做 leading/trailing 拉伸(如 Mail 的 filter 与 compose 分布在 bottomBar 两端)。
  • 单个 item 想脱离背景组(比如头像)用 .sharedBackgroundVisibility(.hidden);想在 item 上加红点用 .badge(count)

Search:把 searchable 放在 NavigationSplitView 上,系统在 iPhone 自动放底部、iPad/Mac 放顶部 trailing(11:44)。

// Search in the top-trailing position

struct TopTrailingSearch: View {
    @State private var searchText = ""

    var body: some View {
        NavigationSplitView {
            SidebarContent()
        } detail: {
            DetailContent()
        }
        .searchable(text: $searchText)
    }
}

关键点:

  • searchable 放在 NavigationSplitView 上表示搜索作用于整个视图,而不是某一列。
  • iPhone 形态自动把搜索框移到屏幕底部、便于拇指触达;iPad/Mac 自动放到 toolbar trailing。
  • 多 tab app 想要专门的搜索页:Tab(role: .search) { ... } + searchableTabView 上;用户切到该 tab 时,搜索框会替换 tab bar。
  • 想强制最小化为 toolbar button,用 .searchToolbarBehavior(.minimize)

Controls:按钮默认胶囊形(小尺寸 macOS 仍是圆角矩形);新增 .glass / .glassProminent 样式(15:33)。

// Prominent glass button
Button("Get Started") { }
    .buttonStyle(.glassProminent)

// Standard glass button
Button("Learn More") { }
    .buttonStyle(.glass)

关键点:

  • .glassProminent 用于行动号召(CTA),自动带 tint。
  • .glass 是普通玻璃按钮,背景跟随下层内容自适应。
  • Slider 新增 tick marks(基于 step 自动生成)和 neutralValue(指定 0 点偏移,用于 EQ 这类双向值)。
  • .rect(corner: .containerConcentric) 做角同心,避免手动算圆角半径。

自定义 Liquid Glass:任意 view 加 .glassEffect(),多元素用 GlassEffectContainer 组合做 morphing(19:51)。

// GlassEffectContainer

@Namespace var namespace

GlassEffectContainer {
    VStack {
        if isExpanded {
            VStack(spacing: 16) {
                ForEach(badges) { badge in
                    BadgeLabel(badge: badge)
                        .glassEffect()
                        .glassEffectID(badge.id, in: namespace)
                }
            }
        }

        BadgeToggle()
            .buttonStyle(.glass)
            .glassEffectID("badgeToggle", in: namespace)
    }
}

关键点:

  • GlassEffectContainer 让内部的多个 glass 元素共用一个采样区域,避免每个元素独立采样产生的视觉割裂。
  • glassEffectID + 同一个 @Namespace,让展开/收起时元素之间产生流体 morph 过渡。
  • 单个元素可用 .glassEffect(.regular.tint(.green)) 染色、.glassEffect(.regular.interactive()) 让玻璃在点按时缩放和高光。

核心启发

  • 做什么:先把项目用 Xcode 26 重新编译一次,记录哪些 UI 自动变好、哪些坏掉了。

    • 为什么值得做:很多新效果(toolbar 分组、sheet 内嵌玻璃、scroll edge effect)零代码就能拿到,先过一遍能省掉一半的迁移工作量。
    • 怎么开始:在新分支上切到 Xcode 26,build & run 各个主要页面,把视觉变化截图对比放到一个 review doc 里,再根据问题清单决定哪些动 API。
  • 做什么:清理旧版本里为对抗 toolbar/sheet 默认样式而手加的 hack(自定义背景、半透明遮罩、presentationBackground)。

    • 为什么值得做:新设计的 automatic scroll edge effect 与 Liquid Glass 材质会和这些旧 hack 互相干扰,越保留视觉越糟。
    • 怎么开始:在项目里搜 presentationBackground.background(.regularMaterial)、自定义 Color.black.opacity 之类,逐个评估能不能直接删掉看默认效果。
  • 做什么:把 toolbar item 按语义重新分组,用 ToolbarSpacer(.fixed) 显式表达。

    • 为什么值得做:用户在新设计里会通过 glass 背景的连续性判断哪些按钮是同一组(如「favorite + add to collection」是一组、share 与 inspector 各自独立)。语义不清的工具栏会让用户误点。
    • 怎么开始:列出每个页面 toolbar 的全部 button,按「相关动作」打括号;每两组之间插一个 ToolbarSpacer(.fixed);脱离组的 item 加 .sharedBackgroundVisibility(.hidden)
  • 做什么:把 searchable 上提到 NavigationSplitView 这一层,删掉为 iPhone/iPad 写的双套布局。

    • 为什么值得做:系统自动在 iPhone 把搜索框落到底部、在 iPad/Mac 放到 toolbar trailing,一份代码两端都对。
    • 怎么开始:把现有 searchable 从 detail 列移到 NavigationSplitView 外层;如果搜索不是核心功能,加 .searchToolbarBehavior(.minimize) 让它自动收起。
  • 做什么:在自研控件外层试一次 glassEffect + GlassEffectContainer,看能不能替换掉手写的卡片样式。

    • 为什么值得做:自定义控件套上 glass 后能跟系统组件视觉一致,而且 GlassEffectContainer 提供的 fluid morphing 是手写动画很难做到的。
    • 怎么开始:选一个有「展开/收起」交互的自研组件(如悬浮工具栏、表情面板)作为实验对象,给每个子元素加 glassEffectID,把外层包成 GlassEffectContainer,对比新旧动画效果。

关联 Session

评论

GitHub Issues · utterances