SwiftUI在使用.toolbarRole(.editor) 且禁用Liquid Glass时,返回按钮的左箭头在带有弹跳动画时出现故障
我有一个全新的SwiftUI应用,为了避免Liquid Glass的重新设计,我在Info.plist中添加 UIDesignRequiresCompatibility = YES。
在一个推送到 NavigationStack 的详情屏幕上,我应用 .toolbarRole(.editor),以使返回按钮只显示箭头(而不在旁边显示上一屏的标题文本)。使用该修饰符时,返回按钮箭头在推送过渡期间会出现glitchy动画:到达详情屏幕时,箭头向下移动,再往上回弹,最终稳定在左上角的正确位置。
这里 有一个简短视频,展示了这个glitchy的返回按钮动画。
我观察到 / 尝试的做法
- 这个glitch仅在禁用Liquid Glass的情况下才会发生。如果把
UIDesignRequiresCompatibility=NO(让应用使用新的Liquid Glass设计),返回按钮的过渡是完全平滑的。 - 移除
.toolbarRole(.editor)会让glitch消失,但这样返回按钮就会显示上一屏的标题文本,这正是我想要避免的。 - 如果父视图的标题是inline而不是large时,glitch也不会发生:在
ParentView设置.navigationBarTitleDisplayMode(.inline)(而不是.large)会得到平滑的返回按钮过渡。 - 它可在 Xcode Preview、Simulator 以及实体设备上重现。
struct ParentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
Text("HI")
.navigationTitle("My Items")
.navigationDestination(for: String.self) { route in
switch route {
case "View2":
DetailView()
default:
EmptyView()
}
}
.navigationBarTitleDisplayMode(.large)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
path.append("View2")
} label: {
Image(systemName: "plus")
}
}
}
}
}
}
struct DetailView: View {
var body: some View {
Text("Detail View")
.navigationTitle("Detail Screen")
.toolbarRole(.editor)
}
}
环境
- Xcode 26.5
- 最低部署目标:iOS 26
UIDesignRequiresCompatibility=YES(Liquid Glass禁用)。将其设为NO会使问题消失。- 可在 Xcode Preview、Simulator 以及真实设备上重现。
问题
为何只有在禁用 Liquid Glass(UIDesignRequiresCompatibility = YES)且父标题为 .large 的情况下,.toolbarRole(.editor) 才会在推送过渡期间让返回按钮的箭头发生动画/抖动?以及在不触发此动画glitch的前提下,如何保留仅箭头的返回按钮?
解决方案
正如评论中所提及,苹果在WWDC26期间确认Xcode 27将不再支持opt-out(选择退出)。该说法可在 Platforms State of the Union 的0:41:03看到。这意味着,UIDesignRequiresCompatibility 将无法在Xcode 27上工作。
基于App Store提交的当前要求,我预期苹果会要求从2027年 4月起提交的应用必须使用Xcode 27构建。
因此,如果你在短时间内仍无法准备好采用liquid glass,应该考虑寻找其他方式来保留旧的外观。
一般而言,工具栏按钮的外观可以通过应用自定义的 ButtonStyle 来进行自定义。这种方法可以用于任何类型的自定义样式。以下演示了如何使用这种技术来对按钮进行样式化,使其在所有iOS版本上看起来一致,模拟旧的外观:
- 创建一个
ButtonStyle,它模仿旧的样式,并用于对作为工具栏项添加的按钮进行样式化。类似如下:
struct NavButtonStyle: ButtonStyle {
let alignment: Alignment
private var sidePadding: CGFloat {
if #available(iOS 26.0, *) {
-4
} else {
0
}
}
func makeBody(configuration: Configuration) -> some View {
configuration.label
.frame(minWidth: 44, minHeight: 44, alignment: alignment)
.padding(alignment == .leading ? .leading : .trailing, sidePadding)
.fontWeight(.semibold)
.foregroundStyle(.tint)
}
}
- 为了方便,你可能想定义一个相应的对
ButtonStyle的扩展:
extension ButtonStyle where Self == NavButtonStyle {
static func navButton(alignment: Alignment) -> Self {
Self(alignment: alignment)
}
}
- 随着iOS 26及以上,工具栏项背面的玻璃背景也需要隐藏。通过对
ToolbarItem应用.sharedBackgroundVisibility(.hidden)来实现。这一修饰符仅在iOS 26及以上可用。我对 SwiftUI NavigationStack ToolbarItem shows unwanted circular background behind icon 的回答展示了如何为较早的iOS版本添加对ToolbarContent的扩展。
对于返回按钮的情况,还需要以下附加步骤:
- 与其应用
.toolbarRole(.editor),不如通过应用.navigationBarBackButtonHidden()来隐藏默认的返回按钮。 - 在详情视图中,添加环境值
dismiss。 - 将你自定义的返回按钮作为工具栏项添加,放置在
.topBarLeading。点击时应调用dismiss()。
下面是将你的示例更新为使用上述按钮样式和扩展的方法:
struct ParentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
Text("HI")
.navigationTitle("My Items")
.navigationDestination(for: String.self) { route in
switch route {
case "View2":
DetailView()
default:
EmptyView()
}
}
.navigationBarTitleDisplayMode(.large)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
path.append("View2")
} label: {
Image(systemName: "plus")
}
.buttonStyle(.navButton(alignment: .trailing))
}
// See https://stackoverflow.com/a/79791386/20386264
.hideSharedBackgroundIfAvailable()
}
}
}
}
struct DetailView: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
Text("Detail View")
.navigationTitle("Detail Screen")
// .toolbarRole(.editor)
.navigationBarBackButtonHidden()
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button("Back", systemImage: "chevron.backward") {
dismiss()
}
.buttonStyle(.navButton(alignment: .leading))
}
// See https://stackoverflow.com/a/79791386/20386264
.hideSharedBackgroundIfAvailable()
}
}
}
在iPhone 17模拟器上运行、iOS 26.5时的效果如下:

在较旧的iOS版本(iOS 26之前)上运行时看起来也非常相似。你也会发现动画没有glitch,因此帖子原先的问题也因此成为副作用得到解决。
不幸的是,看起来在隐藏原生返回按钮时,滑动返回手势似乎不可用,因此这是一个折衷。帖子 Hide navigation bar without losing swipe back gesture in SwiftUI 介绍了一些可能的解决方法。