SwiftUI导航栏模糊效果与自定义过渡兼容异常问题
iOS导航栏模糊效果与自定义3D过渡冲突的修复方案
问题描述
iOS导航栏的内置模糊效果在自定义3D折叠过渡时表现异常,切换两个列表视图时,导航栏模糊会出现错位、闪烁等不一致的问题。复现代码如下:
import SwiftUI struct ContentView: View { @State var showingList = true var body: some View { NavigationView { VStack { if showingList { List { Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) } .listStyle(.plain) .border(.red) .transition(.openInLeft) } else { List { Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) } .listStyle(.plain) .border(.red) .transition(.openInRight) } } .animation(.linear, value: showingList) .toolbar(content: { ToolbarItem(placement: .navigationBarLeading, content: { Button("toggle") { showingList.toggle() } }) }) .background(.black) .navigationTitle("test") .navigationBarTitleDisplayMode(.inline) } } } struct FoldModifier: ViewModifier { let amount: Double let anchor: UnitPoint func body(content: Content) -> some View { if anchor == .leading || anchor == .trailing { content.rotation3DEffect(.degrees(amount), axis: (x: 0, y: 1, z: 0), anchor: anchor) } else if anchor == .top || anchor == .bottom { content.rotation3DEffect(.degrees(amount), axis: (x: 1, y: 0, z: 0), anchor: anchor) } } } extension AnyTransition { static var openInLeft: AnyTransition { .modifier( active: FoldModifier(amount: 90, anchor: .leading), identity: FoldModifier(amount: 0, anchor: .leading) ) } static var openInRight: AnyTransition { .modifier( active: FoldModifier(amount: -90, anchor: .trailing), identity: FoldModifier(amount: 0, anchor: .trailing) ) } }
解决方案
方案1:自定义导航栏替代系统导航栏
隐藏系统导航栏,手动实现带模糊效果的自定义导航栏,彻底避免系统模糊图层的冲突:
struct CustomNavigationBar: View { var title: String var onToggle: () -> Void var body: some View { ZStack { UIBlurEffect(style: .systemMaterial) .ignoresSafeArea() HStack { Button("toggle") { onToggle() } .foregroundColor(.blue) Spacer() Text(title) .font(.headline) Spacer() } .padding() } .frame(height: 44) } } // 修改后的ContentView struct ContentView: View { @State var showingList = true var body: some View { ZStack { Color.black.ignoresSafeArea() VStack(spacing: 0) { CustomNavigationBar(title: "test", onToggle: { showingList.toggle() }) if showingList { List { Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) } .listStyle(.plain) .border(.red) .transition(.openInLeft) } else { List { Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) } .listStyle(.plain) .border(.red) .transition(.openInRight) } } .animation(.linear, value: showingList) } } }
方案2:过渡期间临时隐藏导航栏
利用navigationBarHidden在过渡执行时隐藏系统导航栏,完成后再恢复,避开模糊渲染的冲突:
struct ContentView: View { @State var showingList = true @State var isTransitioning = false var body: some View { NavigationView { VStack { if showingList { List { Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) } .listStyle(.plain) .border(.red) .transition(.openInLeft) } else { List { Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) } .listStyle(.plain) .border(.red) .transition(.openInRight) } } .animation(.linear(duration: 0.3), value: showingList) .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button("toggle") { isTransitioning = true withAnimation { showingList.toggle() } DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { isTransitioning = false } } } } .background(.black) .navigationTitle("test") .navigationBarTitleDisplayMode(.inline) .navigationBarHidden(isTransitioning) } } }
方案3:改用ZStack管理视图过渡
将两个列表放在ZStack中,通过opacity控制显示状态,减少条件渲染带来的图层合成冲突:
struct ContentView: View { @State var showingList = true var body: some View { NavigationView { ZStack { List { Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) } .listStyle(.plain) .border(.red) .transition(.openInLeft) .opacity(showingList ? 1 : 0) List { Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) Text("Hello, world!") .foregroundColor(.red) .font(.system(size: 70)) } .listStyle(.plain) .border(.red) .transition(.openInRight) .opacity(showingList ? 0 : 1) } .animation(.linear, value: showingList) .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button("toggle") { showingList.toggle() } } } .background(.black) .navigationTitle("test") .navigationBarTitleDisplayMode(.inline) } } }
问题原因
该问题属于SwiftUI框架的已知bug,系统导航栏的模糊渲染图层与自定义3D过渡的图层合成逻辑存在冲突,导致过渡期间模糊效果异常。上述方案通过绕过系统导航栏模糊或调整过渡逻辑来解决该问题。
内容的提问来源于stack exchange,提问作者Falco
相关产品推荐
相关产品推荐

