Highlight
macOS Tahoe 给 AppKit 引入 Liquid Glass 材质、浮动 toolbar 与 sidebar、新的窗口圆角与控件尺寸,多数效果用 Xcode 26 重编即可生效,剩余适配通过
NSGlassEffectView、NSBackgroundExtensionView、NSView.LayoutRegion等新 API 完成。
核心内容
老的 AppKit 应用在 macOS Tahoe 上跑起来,最常见的麻烦是视觉错位。Sidebar 里塞着一层 NSVisualEffectView,把新的玻璃材质完全挡住;toolbar 上的状态文字被自动套上玻璃背景,看起来像一个伪按钮;窗口圆角变大以后,靠近角落的”新建文件夹”按钮被裁掉一块;高密度 Inspector 因为控件整体变高而错位。这是 Apple 在 Session 一开始就摆出来的迁移现实。
Apple 把改动按从外到内的顺序拆成四段(00:52):App 结构(toolbar、sidebar、窗口圆角)、Scroll Edge Effect(滚动内容与玻璃元素重叠时的视觉)、Controls(新尺寸、新形状、prominence、玻璃 bezel)、Liquid Glass 自定义(NSGlassEffectView / NSGlassEffectContainerView)。绝大多数效果在 Xcode 26 重编后直接生效——toolbar 自动分组到玻璃上、sidebar 自动浮动、控件自动换上新外观。需要手动介入的地方,每一处都对应一个具体的 API,没有靠”重写整个 view”来解决的项目。
详细内容
Toolbar 的玻璃由 NSToolbar 自动套上(02:53)。同类按钮被合并到一块玻璃上,segmented、pop-up、search 这些不同类型会被拆开。问题在于,状态文字、自定义标题这种非交互内容也会被加玻璃,看起来像按钮。解法是关掉 bordered:
// Removing toolbar item glass
toolbarItem.isBordered = false
关键点:
isBordered = false让NSToolbarItem不显示玻璃背景,适合 Photos toolbar 里那种状态文字(03:11)。- 反过来,要强调状态或重要操作,可以给 toolbar item 设置
style = .prominent,玻璃会被 accent color 染色;也可以用backgroundTintColor指定颜色(03:30)。 - 想表达”有未读”或”有新通知”,用
NSItemBadge.count(_:)、NSItemBadge.text(_:)、NSItemBadge.indicator(03:58)。
Sidebar 在新设计里浮在内容之上(04:12)。如果想让横向滚动内容、地图、海报这种富内容延伸到 sidebar 下方,开一个开关:
// Content under the sidebar
splitViewItem.automaticallyAdjustsSafeAreaInsets = true
关键点:
- 这个属性要设在被 sidebar 遮住的内容侧,不要设到 sidebar 自己(05:25)。
NSSplitView会把 frame 延伸到 sidebar 之下,再用 safe area layout guide 让内容只在未被遮住的区域里布局。- 对于本身没有”留白”的艺术作品,用
NSBackgroundExtensionView:把它铺满 split item,给它一个 contentView,AppKit 会自动镜像并模糊出 safe area 之外的延伸(06:24)。 - 别忘了移除 sidebar 里旧的
NSVisualEffectView,否则会挡住新的玻璃材质(04:46)。
窗口圆角变大,按钮容易撞角(07:07)。带 toolbar 的窗口圆角更大,跟玻璃 toolbar 同心。靠近角落的控件需要新的 layout region API:
// Avoiding a window corner
func updateConstraints() {
guard !installedButtonConstraints else { return }
let safeArea = layoutGuide(for: .safeArea(cornerAdaptation: .horizontal))
NSLayoutConstraint.activate([
safeArea.leadingAnchor.constraint(equalTo: button.leadingAnchor),
safeArea.trailingAnchor.constraint(greaterThanOrEqualTo: button.trailingAnchor),
safeArea.bottomAnchor.constraint(equalTo: button.bottomAnchor)
])
installedButtonConstraints = true
}
关键点:
layoutGuide(for: .safeArea(cornerAdaptation: .horizontal))返回一个比普通 safe area 多一段水平内缩的 guide,用来避开圆角(08:47)。cornerAdaptation可选.horizontal或.vertical,表示在哪个方向给圆角让位。- 也可以从 layout region 直接取 edge insets 或当前 rect,用于非 Auto Layout 场景(08:17)。
Scroll Edge Effect 不需要主动开(09:27)。NSScrollView 会根据上方浮动元素自动调整效果大小,soft-edge 渐变模糊,hard-edge 更不透明。要把自定义浮动控件接到这套效果里,用 NSSplitViewItemAccessoryViewController 加 top/bottom aligned accessory,它会同时影响 scroll edge effect 形状和内容 safe area。
Controls 多了一个 extra large 尺寸(11:54)。从 mini 到 extra large 一共五档,mini/small/medium 比之前略高,所以不要硬编码控件高度,用 Auto Layout。高密度场景(Inspector、Popover)可以在 NSView 上设置 prefersCompactControlSizeMetrics = true,沿视图层级继承,控件会回到旧高度(12:56)。
控件形状:mini–medium 是圆角矩形,large/extra large 是胶囊形。需要同心可以用 borderShape 覆写按钮、pop-up、segmented control 的形状(13:52)。需要玻璃按钮就用新的 glass bezel style,配合 bezelColor 染色。
Tint prominence 控制 tint 浓度(15:31):
// Create buttons with varying levels of prominence
// Prefer a "secondary" tinted appearance for the shuffle and enqueue buttons
shuffleButton.tintProminence = .secondary
playNextButton.tintProminence = .secondary
// The "play" will automatically use primary prominence because it is the default button
playButton.keyEquivalent = "\r"
关键点:
tintProminence有.automatic/.none/.secondary/.primary四档。- shuffle、enqueue 这种次级动作用
.secondary,让 tint 减弱、不抢主按钮戏。 - 给 play 按钮
keyEquivalent = "\r"标为默认按钮,自动获得 primary prominence。 - 对 slider 同样有效,
tintProminence = .none不填充 track;新增neutralValue可以把填充锚点放在轨道任意位置,例如播放速度的 1x(16:20)。
Liquid Glass 适配分两步(18:13)。先把要变玻璃的元素包进 NSGlassEffectView:
// Adopting NSGlassEffectView
let userInfoView = UserInfoView()
let activityPickerView = ActivityPickerView()
let userInfoGlass = NSGlassEffectView()
userInfoGlass.contentView = userInfoView
let activityPickerGlass = NSGlassEffectView()
activityPickerGlass.contentView = activityPickerView
let stack = NSStackView(views: [userInfoGlass,
activityPickerGlass])
stack.orientation = .horizontal
关键点:
- 把要”上玻璃”的视图设给
contentView,AppKit 会自动用 Auto Layout 把玻璃几何与内容绑定,并施加保证可读性的视觉处理(18:42)。 - 不要把
NSGlassEffectView当兄弟节点放在内容后面,必须包在外面。 cornerRadius与 tint color 在NSGlassEffectView上可调。
相邻的玻璃元素必须用 NSGlassEffectContainerView 分组:
// Adopting NSGlassEffectContainerView
let userInfoView = UserInfoView()
let activityPickerView = ActivityPickerView()
let userInfoGlass = NSGlassEffectView()
userInfoGlass.contentView = userInfoView
userInfoGlass.cornerRadius = 999
let activityPickerGlass = NSGlassEffectView()
activityPickerGlass.contentView = activityPickerView
activityPickerGlass.cornerRadius = 999
let stack = NSStackView(views: [userInfoGlass,
activityPickerGlass])
stack.orientation = .horizontal
let glassContainer = NSGlassEffectContainerView()
glassContainer.contentView = stack
关键点:
NSGlassEffectContainerView把多块玻璃合并成一次渲染,靠近时玻璃可以液态融合(19:35)。spacing属性控制融合距离阈值。- 玻璃材质会从周围采样取色生成折射,但玻璃采样不到另一块玻璃——不分组会出现颜色不一致;分组让多块玻璃共享一次采样(20:23)。
- 容器与 contentView 同样用 Auto Layout 绑定,可以原地替换原有布局节点(21:03)。
核心启发
1. 用 Xcode 26 重编一遍,先看默认行为再决定改什么
为什么值得做:toolbar 自动分组上玻璃、sidebar 自动浮动、控件自动换尺寸,绝大多数视觉更新是免费的。先看看哪里崩了再针对性修,比预先重写每个界面成本低得多。
怎么开始:换到 Xcode 26 编译当前项目,启动后逐窗口截图,对照原来的截图标出”自动变了但变得不对”的位置。
2. 审计硬编码的控件高度,全部改成 Auto Layout
为什么值得做:mini/small/medium 控件都长高了,加上新增的 extra large,硬编码 heightAnchor.constraint(equalToConstant:) 是这次适配里出问题最密集的地方。Inspector、popover、菜单项布局都会受影响。
怎么开始:grep 工程里 heightAnchor.constraint(equalToConstant、frame.size.height =、bounds.height = 这些写法,把控件相关的全部换掉;对必须维持高密度的旧界面,在父视图上设置 prefersCompactControlSizeMetrics = true。
3. 给菜单项补 symbol icon,并清理 sidebar 里的 NSVisualEffectView
为什么值得做:新设计鼓励菜单全程用 symbol,扫读速度直接提升;sidebar 里残留的 visual effect view 会挡住新的玻璃材质,是最常见的”看起来不像 Tahoe 应用”的元凶。
怎么开始:列出所有 menu item,按”是否有对应 SF Symbol”打钩补齐;在 view debugger 里搜 NSVisualEffectView,确认 sidebar 内的全部移除。
4. Liquid Glass 只用在最高层级控件,并用 Container 分组
为什么值得做:玻璃用来标识”浮在最顶层的控件 / 导航”这一层级,Apple 明确建议限制使用范围。大面积或孤立放置玻璃,会因为采样冲突出现颜色不一致,性能也更差。
怎么开始:找到当前 UI 里”浮在内容之上的工具条 / 编辑控件”,用 NSGlassEffectView 包装;只要相邻有第二块玻璃,就外面套一层 NSGlassEffectContainerView,把 spacing 调到合适的融合距离。
5. 富内容延伸到 sidebar 下方时用 NSBackgroundExtensionView 兜底
为什么值得做:海报、地图这类作品没有为 sidebar 留白,硬延伸会切掉关键信息。Background Extension View 自动镜像模糊出延伸区,让 safe area 内的真实内容不变。
怎么开始:先开 splitViewItem.automaticallyAdjustsSafeAreaInsets = true;对没有负空间的素材,用 NSBackgroundExtensionView 包一层,contentView 只放真正要展示的图,延伸区交给系统生成。
关联 Session
- Build a SwiftUI app with the new design — SwiftUI 这边的同款新设计适配指南,跟 AppKit 版本对照看
- Get to know the new design system — Liquid Glass 与新设计系统的总览,本场多次引用
- Elevate the design of your iPad app — iPadOS 上同一套设计语言的落地差异
- Create icons with Icon Composer — 配套的图标制作工具,用来给菜单和 toolbar 配 symbol
- Bring Swift Charts to the third dimension — 内容侧的更新,与新设计配合呈现
评论
GitHub Issues · utterances