SwiftUI 工具栏切换 SF Symbol 时如何避免上下跳动?
数据阿明
结论
不要在启用和未启用状态之间切换整个符号。工具栏中的 Image 会根据当前符号的图像边界参与布局。像 tray.badge 这样高度不同的符号,可能会让图标主体看起来上下移动。.frame 可以固定视图占用的空间,却不能保证不同符号的内部图形沿同一基线对齐。
更稳妥的做法是始终显示同一个基础图标,启用时再用 overlay 在图标角上叠加标记。底图保持不变,工具栏布局也不会因切换符号而改变。
用 overlay 叠加标记
例如,保留未带标记的筛选图标,启用筛选时在角上叠加一个小圆点:
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
isFilterSheetPresented = true
} label: {
Image(systemName: "custom.line.3.horizontal.decrease")
.symbolRenderingMode(.hierarchical)
.foregroundStyle(
activeFilterCount > 0 ? Color.accentColor : Color.primary
)
.overlay(alignment: .topTrailing) {
if activeFilterCount > 0 {
Circle()
.fill(Color.accentColor)
.frame(width: 7, height: 7)
}
}
}
.accessibilityLabel(
activeFilterCount > 0
? "Filter, \(activeFilterCount) active"
: "Filter"
)
}
}
需要显示筛选数量时,可以将圆点换成带数字的标记视图。overlay(alignment:) 根据固定底图的视图边界放置标记,因此文本大小变化时,底图的位置不会跟着调整。
对齐方式的限制
.alignmentGuide() 和 .firstTextBaseline 可以控制 SwiftUI 布局中的视图对齐,但不能直接控制工具栏中不同 SF Symbol 图形内容的对齐。符号的边界框与可见图形各不相同,所以固定外层 frame 未必能消除图形本身的视觉位移。
如果必须切换完整符号,可以尝试用相同尺寸的容器固定视图空间,但符号内部的图形仍可能不齐。也可以根据文字大小逐一调整 .offset(y:),不过这种做法依赖具体符号和字号,维护起来比较脆弱。
注意事项
- 确保叠加标记的颜色和大小在浅色、深色外观下都清晰可见。
- 如果标记表示“筛选已启用”,通常用小圆点就够了;要表示启用数量时,可以直接显示数量,并设置合适的辅助功能标签。
- 如果确实需要使用
tray.badge或自定义的带标记符号,需考虑不同符号的图形边界可能造成视觉位置变化。固定frame无法保证符号内部图形对齐。
备注:内容仅供参考。
