skip to Main Content

I try to create a custom ViewModifier similar to SwiftUI’s .sheet modifier.

When I try to make a NavigationView spring from bottom, the frame of the view just glitched over safearea. The frame looks as if adjust to the safearea when the view moves from bottom to top.

Anyone knows maybe how to constrain the view frame inside the navigation view to avoid this?

Here is what happened. When click the plus button, the SwiftUI .sheet modifier shows up. Custom popup shows up when pressing the gear button.
Problem gif recording here

Here is code of the custom popup view.

struct SettingsView: View {
    @Binding var showingSelf: Bool
    @Binding var retryWrongCards: Bool
        
    var body: some View {
        GeometryReader { geometry in
            NavigationView {
                List {
                    Section {
                        Toggle(isOn: $retryWrongCards) {
                            Text("Retry Wrong Cards")
                        }
                    }
                }
                .animation(nil)
                .navigationViewStyle(StackNavigationViewStyle())
                .listStyle(GroupedListStyle())
                .navigationBarTitle("Settings")
                .navigationBarItems(trailing: Button("Done") {
                    self.showingSelf = false
                })
            }
        }
    }
}

Here’s the code of custom modifier

struct Popup<T: View>: ViewModifier {
    let popup: T
    let isPresented: Bool

    init(isPresented: Bool, @ViewBuilder content: () -> T) {
        self.isPresented = isPresented
        popup = content()
    }

    func body(content: Content) -> some View {
        content
            .overlay(popupContent())
    }

    @ViewBuilder private func popupContent() -> some View {
        GeometryReader { geometry in
            if isPresented {
                popup
                    .animation(.spring())
                    .transition(.offset(x: 0, y: geometry.belowScreenEdge))
                    .frame(width: geometry.size.width, height: geometry.size.height)
            }
        }
    }
}

private extension GeometryProxy {
    var belowScreenEdge: CGFloat {
        UIScreen.main.bounds.height - frame(in: .global).minY
    }
}

2

Answers


  1. It seems your animation does exactly what a sheet does. I would just replace it with that. You can easily present a sheet on top of a sheet.

    Edit

    After you posted your ViewModifier code, it seems the only difference is the animation type and the size of the popup.

    Login or Signup to reply.
  2. After debugging and trying many changes, I was already preparing a mini-project to file a SwiftUI bug for Apple. In the end, it was not necessary and we have a simple solution 🥳!!

    TLDR; your SettingsView does not correctly initialise the NavigationView.
    The modifier .navigationViewStyle(StackNavigationViewStyle()) has to be applied onto the NavigationView and not inside it (in contrast to navigationBarTitle or navigationBarItems which only work when put inside the NavigationView):

    NavigationView {
        List {
            Section {
                Toggle(isOn: $retryWrongCards) {
                    Text("Retry Wrong Cards")
                }
            }
        }
        .animation(nil)
        .listStyle(GroupedListStyle())
        .navigationBarTitle("Settings")
        .navigationBarItems(trailing: Button("Done") {
            self.showingSelf = false
        })
    }
    .navigationViewStyle(StackNavigationViewStyle())
    

    After this change your custom sheet modifier behaves for me identically as the regular .sheet modifier! Great work!
    Philipp

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search