You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 23:31:12