Highlight
visionOS 2 引入 Custom Hover Effect API,让视图在用户注视、手指靠近或鼠标悬停时响应变化,系统在 app 进程外应用效果以保护隐私。
核心内容
在 visionOS 上,当用户看向交互元素时,系统会自动应用高亮效果。这些 Hover Effect 让 app 感觉有响应性,同时提供反馈告诉用户哪个元素会被触发。标准高亮效果对大多数场景够用了,但有些视图需要自定义效果——Slider 显示旋钮来引导交互、返回按钮展开显示上一页名称、Tab Bar 弹出显示标签、Safari 导航栏展开显示标签页。
visionOS 2 引入的 Custom Hover Effect API 从设计之初就考虑了隐私保护。效果由系统在 app 进程之外应用,不需要额外的 entitlement 或 extension。自定义效果可以应用在 SwiftUI 视图的任何位置,包括 ornaments 和 RealityView 的 attachment。触发方式不只是眼动追踪,还包括手指靠近和鼠标悬停。
API 的核心概念分为四层:Content Effect(改变视图外观的基础效果,包括缩放、裁剪、透明度)、Effect Group(让多个效果协同触发)、Delayed Effect(控制效果的触发时机)、以及 CustomHoverEffect 协议(创建可复用的、尊重无障碍偏好的自定义效果)。
详细内容
Content Effect:基础外观变化
Content Effect 是改变视图外观的基础效果,支持三种类型:缩放(scaleEffect)、裁剪(clipShape)和透明度(opacity)。这些效果只改变视图的视觉呈现,不影响布局。
最简单的是缩放效果。在 ButtonStyle 中使用 hoverEffect 修饰符:
// Button with Scale Effect([04:06](https://developer.apple.com/videos/play/wwdc2024/10152/?time=246))
ButtonStyle {
configuration
.hoverEffect(.highlight) // 标准高亮
.hoverEffect(isActive: isActive in
configuration.label
.scaleEffect(isActive ? 1.05 : 1.0) // 激活时放大 5%
)
}
关键点:
hoverEffect块会被调用两次,isActive分别为 true 和 false,用于定义激活和未激活状态- 系统会预先调用这些块生成效果,而非在用户注视时实时调用
- 5% 的缩放是经过验证的舒适幅度
Clip Effect 用于隐藏和揭示内容。通过改变 clipShape 的大小来控制可见区域:
// Button with Clip and Scale Effects([05:37](https://developer.apple.com/videos/play/wwdc2024/10152/?time=337))
ButtonStyle {
configuration
.hoverEffect(.highlight)
.hoverEffect(isActive: isActive, proxy: proxy in
configuration.label
.clipShape(
Capsule()
.size(
width: isActive ? proxy.size.width : proxy.size.height,
height: proxy.size.height
)
.anchor(isActive ? .center : .leading) // 非激活时对齐左侧,只显示图标
)
.scaleEffect(isActive ? 1.05 : 1.0)
)
}
关键点:
proxy参数提供视图的几何信息- 激活时 clipShape 跨越按钮全宽,非激活时变成正方形只显示图标
anchor参数控制 clipShape 的对齐方式
Effect Group:协同多个效果
当多个效果需要协同触发时,需要使用 Effect Group。默认情况下,效果只在用户注视该视图时激活。如果把缩放和裁剪应用到整个按钮,而把透明度应用到详情文字,就会出现问题——只有注视文字时才会淡入。
解决方案是使用 Effect Group,让所有效果一起激活。有两种方式:
显式分组(最灵活的控制):
// Expanding Button with Explicit Group([08:19](https://developer.apple.com/videos/play/wwdc2024/10152/?time=499))
@Namespace private var profileButtonGroup
@State private var group = HoverEffectGroup()
var body: some View {
Button { ... } label: {
Label("Profile", systemImage: "person.circle")
}
.buttonStyle(ProfileButtonStyle(group: group))
}
struct ProfileButtonStyle: ButtonStyle {
var group: HoverEffectGroup
func makeBody(configuration: Configuration) -> some View {
configuration.label
.hoverEffect(.highlight)
.hoverEffect(isActive: ..., in: group) // 将效果加入组
}
}
隐式分组(最便捷):
// Expanding Button with Implicit Group([09:13](https://developer.apple.com/videos/play/wwdc2024/10152/?time=553))
Button { ... } label: {
Label("Profile", systemImage: "person.circle")
}
.hoverEffectGroup() // 自动将所有子视图的效果加入组
关键点:
- 显式分组需要创建
HoverEffectGroup并用 Namespace 标识 - 隐式分组只需在父视图添加
hoverEffectGroup()修饰符 - 组内任一效果被触发时,整个组的效果都会激活
Delayed Effect:控制触发时机
默认情况下,hover effects 立即触发。但这会造成干扰——用户浏览界面时会频繁激活效果。适当延迟可以避免闪烁。
对于揭示额外内容的效果(如展开按钮),需要更长的延迟。对于提供即时反馈的效果(如缩放),应该立即响应。
// Expanding Button with Delay([10:53](https://developer.apple.com/videos/play/wwdc2024/10152/?time=653))
.hoverEffect(isActive: isActive in
configuration.label
.scaleEffect(isActive ? 1.05 : 1.0) // 缩放立即响应
.clipShape(...)
.opacity(isActive ? 1 : 0)
.animation(
isActive ? .default.delay(0.3) : .default.delay(0.1),
value: isActive
)
)
关键点:
- 激活时延迟更长(0.3 秒),非激活时延迟更短(0.1 秒)
- 缩放效果不需要延迟,因为它提供即时反馈
- 延迟值需要在真机上测试,找到舒适的感觉
CustomHoverEffect 协议:尊重无障碍偏好
对于有运动敏感性的用户,动态效果可能让人不适。通过 CustomHoverEffect 协议,可以根据用户的辅助功能设置调整效果。
// Cross-fade effect for reduced motion([12:37](https://developer.apple.com/videos/play/wwdc2024/10152/?time=757))
struct AccessibilityHoverEffect: CustomHoverEffect {
func body(content: Content, configuration: Configuration) -> some View {
@Environment(\.accessibilityReduceMotion) var reduceMotion
if reduceMotion {
// 使用淡入淡出效果
content.opacity(configuration.isActive ? 1 : 0)
} else {
// 使用完整展开效果
content
.scaleEffect(configuration.isActive ? 1.05 : 1.0)
.clipShape(...)
}
}
}
关键点:
CustomHoverEffect协议要求实现body(content:configuration:)方法accessibilityReduceMotion环境变量指示用户是否启用了减弱动态效果- 应为运动敏感用户提供替代效果
动画支持
Hover Effects 支持常见的 SwiftUI 动画:linear、easeOut、spring、以及自定义 timing curves。但 CustomAnimation 类型不支持,因为它们无法在 app 进程外应用。
核心启发
1. 为揭示性内容添加延迟展开效果
在空间计算界面中,额外内容默认隐藏,注视时才展开。这既减少视觉噪音,又保持信息可访问。适合场景:设置面板的详细选项、列表项的扩展信息、导航菜单的子项。
2. 根据内容类型选择延迟策略
即时反馈类效果(缩放、高亮)应该立即触发,让用户知道元素可交互。揭示性内容类效果(展开、淡入)需要 200-300ms 延迟,避免用户浏览时频繁触发。
3. 为运动敏感用户准备替代方案
检测 accessibilityReduceMotion 偏好,禁用或简化动态效果。可以用淡入淡出替代展开/收缩,或完全移除效果只保留静态高亮。所有涉及运动的 UI 都应该这样做。
关联 Session
- Create custom visual effects with SwiftUI — discover how to create stunning visual effects in swiftui. learn to build unique
- What’s new in RealityKit — realitykit powers the visual experience of visionos. learn about the latest capabilities and enhancements
- Elevate your windowed app for spatial computing — learn how to take your existing windowed app to the next level with spatial computing features
- Bring spatial computing to your web content — learn how to bring your website to life on apple vision pro with immersive web experiences
- Meet SwiftUI for spatial computing — learn how swiftui makes it easy to build great experiences for spatial computing
评论
GitHub Issues · utterances