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) { ... }+searchable放TabView上;用户切到该 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,对比新旧动画效果。
- 为什么值得做:自定义控件套上 glass 后能跟系统组件视觉一致,而且
关联 Session
- Meet Liquid Glass — Liquid Glass 材质的设计原理与光学特性介绍。
- Get to know the new design system — 全平台新设计系统的最佳实践与使用规则。
- Build a UIKit app with the new design — UIKit 侧的同主题迁移指南,可与本篇对照阅读。
- Better together: SwiftUI and RealityKit — 在 visionOS 26 中混用 SwiftUI 与 RealityKit 的新方式。
- Make a big impact with small writing changes — 新设计下文案微调对体验的放大效应。
评论
GitHub Issues · utterances