SwiftUI navigation bar items frame are misaligned after sheet dismiss
Asked Answered
R

3

6

Navigation bar buttons are not tappable after dismissing a sheet in SwiftUI. Below is the steps to reproduce the issue

  1. Present a sheet,
  2. Move the app to background for a short duration (2 seconds)
  3. Resume the app & dismiss the sheet by swiping down

Now the navigation bar button frames are misaligned. Tap is working at different frame than visible frame of the button. This is easily reproducible on iOS 16 simulator, but intermittently on actual iOS devices. Below is the minimal code to reproduce the issue



struct ContentView: View {
    @State private var showSheetView = false

    var body: some View {
        NavigationView {
            VStack {
                navigationBarView
                Color.blue
            }
            .sheet(isPresented: $showSheetView) {
                FilterView()
            }
            .navigationBarHidden(true)
        }
        .navigationViewStyle(.stack)
    }
    
    private var navigationBarView: some View {
        HStack(spacing: 0) {
            Spacer()
            Button {
                showSheetView = true
            } label: {
                Text("Filter")
                    .padding()
                    .background(Color.red)
            }
        }
    }
}

struct FilterView: View {
    var body: some View {
        Color.green
    }
}
Risible answered 17/10, 2022 at 5:44 Comment(2)
Is there a reason why you don't use the toolbar specifically? I got the app to work just fine with the navigation bar and not with your implementation of a VStack.Christinchristina
Yes, my actual code has a customized navigation bar which cannot be achieved with toolbarRisible
T
5

I've also struggled a lot with this issue, and it's clearly a bug from apple.

I found an ugly hack to workaround this issue. But to make it work I have to redraw my main view every time I enter background. For my project it's ok until they fix an actual bug. I hope it will help you as well

When you detect app is moving to background, you force app to redraw based on UUID()

struct BackgroundModeSheetBugApp: App {

    @State private var id = UUID()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .onReceive(NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)) { _ in
                    id = UUID()
                }
                .id(id)
        }
    }
}

I've tested it with your code and it's working correctly on both simulator and device

P.S. I also took courage to use your example to submit a bug to Apple

P.P.S. For my app I've also had to wrap my main view to hack UIViewControllerRepresentable to fix the padding issues.

struct UIHackMainTabView: UIViewControllerRepresentable {

    func makeUIViewController(context: Context) -> UIHostingController<MainTabView> {
        let mainTab = MainTabView()
        return UIHostingController(rootView: mainTab)
    }

    func updateUIViewController(_ pageViewController: UIHostingController<MainTabView>, context: Context) {
    }
}
Teresa answered 27/10, 2022 at 9:11 Comment(3)
Thanks for your response, this workaround works only when app moves to background, the steps in my question was minimal steps to reproduce and the issue is actually happening even when the app is not moved to background.Risible
Can you give me those steps, so I can keep researching?Teresa
If the navigation view has scroll view, and when id changed scroll move to the top item. scroll position shold be the same positionInterplead
T
2

Another workaround for now is to make use of presentationDetents() It's break UI a little, since we're loosing nice shrink effect and it works only on iOS 16, but at least app will work there :(

struct SheetHackModifier: ViewModifier {

    func body(content: Content) -> some View {
        if #available(iOS 16.0, *) {
            content
                .presentationDetents([.fraction(0.99)])
        } else {
            content
        }
    }
}

extension View {
    func sheetHackModifier() -> some View {
        self.modifier(SheetHackModifier())
    }
}
Teresa answered 1/11, 2022 at 8:15 Comment(1)
thanks for the answer, this works until the keyboard is used in the sheet, when the keyboard pop up, the presentDetent somehow becomes to .large, and then if move to background and back in, the misalign appears again...Everetteeverglade
I
0

Finally solved the issue. Some said to use id, but when there is a scroll in the NavigationView and we refresh this id, the scroll comes to the top.

You can use the below solution.

content
        .onDisappear {
            let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene
            if let viewFrame = scene?.windows.first?.rootViewController?.view.frame {
                scene?.windows.first?.rootViewController?.view.frame = .zero
                scene?.windows.first?.rootViewController?.view.frame = viewFrame
            }
        }
Interplead answered 26/7, 2023 at 9:10 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.