简体   繁体   中英

SwiftUI - How to close a fake Modal View - from the second view inside it, with a close button?

I have a tricky design. I'm trying to have a close button that from whatever views, inside a "fake" Modal View (fake cause it's full screen, thanks to a code that i found online), it's gonna close the Modal View. Right now the close button is working in the first view that open in the modal view, but I need it to work also in the next views of the modal view, cause I have a flow inside this modal view to create an item.

This is the View from which I start ColorNewItemView, my fake Modal View.

struct RecapItemToAdd: View {

  @State var isPresented: Bool = false

    var body: some View {
      NavigationView {
        
        VStack {
            
            Button(action: { withAnimation { self.isPresented.toggle()}}) {
            Image(systemName: "pencil")
                .resizable()
                .renderingMode(.original)
                .frame(width: 13, height: 17)
                .foregroundColor(UIManager.hLightGrey)
            }

        }
        
        ZStack {
             VStack(alignment: .leading) {
                 ColorNewItemView(isPresenteded: self.$isPresented)

                 Spacer()
               }


         }
         .background(Color.white)
         .edgesIgnoringSafeArea(.all)
         .offset(x: 0, y: self.isPresented ? 0 : UIApplication.shared.keyWindow?.frame.height ?? 0)
    }

   }
 
}

Note: I know that "keyWindow" is deprecated but I don't know how to change it.

With ColorNewItemView starts my full screen Modal View. In this view the close button works.

   struct ColorNewItemView: View {

   @State var selection: Int? = nil

   @Binding var isPresenteded: Bool

      var body: some View {
      NavigationStackView {
         VStack(alignment: .center) {

          Button(action: {
                  self.isPresenteded = false
              }) {
                  Image(systemName: "xmark.circle.fill")
                  .resizable()
                  .frame(width: 30, height: 30)
                  .foregroundColor(UIManager.hBlueLight)
              }
    
    Text("First View")
            .font(UIManager.einaTitle)
            .foregroundColor(UIManager.hDarkBlue)
    
    
    Image("black-hoodie")
    .resizable()
    .renderingMode(.original)
    .frame(width: 245, height: 300)
    
    PushView(destination: Color2NewItemView(isPresenteded: self.$isPresenteded), tag: 1, selection: $selection) {
                  
                 Button(action: {self.selection = 1}) {
                 Text("Avanti")
                      .font(UIManager.einaButton)
                      .foregroundColor(.white)
                      .frame(width: 291, height: 43)

                      .background(UIManager.buttonGradient)
                      .cornerRadius(6)
                      .shadow(color: UIManager.hBlueShadow, radius: 7, x: 0.0, y: 6.0)
                 }
              }

        }
    }
  }
 }

Now I have the next view inside the Modal view, where the close button starts to stop working.

    struct Color2NewItemView: View {

    @Binding var isPresenteded: Bool
    @State var selection: Int? = nil

    var body: some View {
      VStack(alignment: .center) {


                 Button(action: {
                       self.isPresenteded = false
                   }) {
                       Image(systemName: "xmark.circle.fill")
                       .resizable()
                       .frame(width: 30, height: 30)
                       .foregroundColor(UIManager.hBlueLight)
                   }

        Text("Second View")
                .font(UIManager.einaTitle)
                .foregroundColor(UIManager.hDarkBlue)


        Image("black-hoodie")
            .resizable()
            .renderingMode(.original)
            .frame(width: 245, height: 300)


        PushView(destination: FabricNewItemView(isPresenteded: $isPresenteded), tag: 1, selection: $selection) {

                         Button(action: {self.selection = 1}) {
                         Text("Tessuto")
                              .font(UIManager.einaButton)
                              .foregroundColor(.white)
                              .frame(width: 291, height: 43)

                              .background(UIManager.buttonGradient)
                              .cornerRadius(6)
                              .shadow(color: UIManager.hBlueShadow, radius: 7, x: 0.0, y: 6.0)
                         }
        }

                        Spacer()
                        .frame(height: 18)


                    PopView{
                        Text("Back")
                        .font(UIManager.einaBodySemibold)
                        .foregroundColor(UIManager.hGrey)
                    }

        }
    }
}

Ps. I had also to use a library called NavigationStack, since I have a custom back button on the bottom of the page, and the Navigation View doesn't let me pop back without using the back in the navigation bar.

Binding can be lost on deep view hierarchy, so it is more appropriate to operate with it on the level it was received.

Here is possible approach using EnvironmentKey (by same idea as presentationMode works)

Introduce helper environment key which holds some closure

struct DismissModalKey: EnvironmentKey {

    typealias Value = () -> ()
    static let defaultValue = { }
}

extension EnvironmentValues {
    var dismissModal: DismissModalKey.Value {
        get {
            return self[DismissModalKey.self]
        }
        set {
            self[DismissModalKey.self] = newValue
        }
    }
}

so in your top modal view you can inject into hierarchy callback to dismiss

struct ColorNewItemView: View {

   @State var selection: Int? = nil
   @Binding var isPresented: Bool

   var body: some View {
      NavigationStackView {
          // ... other code
      }
      .environment(\.dismissModal, { self.isPresented = false} )   // << here !!
   }
}

thus this environment value now is available for all subviews, and you can use it as

struct Color2NewItemView: View {
   @Environment(\.dismissModal) var dismissModal
   
   @State var selection: Int? = nil

   var body: some View {
      VStack(alignment: .center) {
          Button(action: {
              self.dismissModal()       // << here !!
          }) {
      
      // ... other code
   }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM