36

I've presented a modal view but I would like the user to go through some steps before it can be dismissed. Currently the view can be dragged to dismiss.

Is there a way to stop this from being possible?

I've watched the WWDC Session videos and they mention it but I can't seem to put my finger on the exact code I'd need.

struct OnboardingView2 : View {

    @Binding
    var dismissFlag: Bool

    var body: some View {

        VStack {
            Text("Onboarding here! ")
            Button(action: {
                self.dismissFlag.toggle()
            }) {
                Text("Dismiss")
            }
        }
    }
}

I currently have some text and a button I'm going to use at a later date to dismiss the view.

Matteo Pacini
  • 21,796
  • 7
  • 67
  • 74
Rob Peach Mellor
  • 361
  • 1
  • 3
  • 3

6 Answers6

25

iOS 15+

Starting from iOS 15 we can use interactiveDismissDisabled:

func interactiveDismissDisabled(_ isDisabled: Bool = true) -> some View

We just need to attach it to the sheet. Here is an example from the documentation:

struct PresentingView: View {
    @Binding var showTerms: Bool

    var body: some View {
        AppContents()
            .sheet(isPresented: $showTerms) {
                Sheet()
            }
    }
}

struct Sheet: View {
    @State private var acceptedTerms = false
    
    var body: some View {
        Form {
            Button("Accept Terms") {
                acceptedTerms = true
            }
        }
        .interactiveDismissDisabled(!acceptedTerms)
    }
}
Joannes
  • 2,569
  • 3
  • 17
  • 39
pawello2222
  • 46,897
  • 22
  • 145
  • 209
9

It is easy if you use the 3rd party lib Introspect, which is very useful as it access the corresponding UIKit component easily. In this case, the property in UIViewController:

VStack { ... }
.introspectViewController {
    $0.isModalInPresentation = true
}
samwize
  • 25,675
  • 15
  • 141
  • 186
  • Wow thank you so much, took me seconds through swift package manager and seconds to add it.. – Kai Zheng Oct 19 '20 at 05:30
  • 1
    This worked for me, but only if I applied the modifier to the root view in the `.sheet` modifier. If I applied it to subviews, it had no effect. – gnarlybracket Feb 03 '21 at 21:15
1

Not sure this helps or even the method to show the modal you are using but when you present a SwiftUI view from a UIViewController using UIHostingController

let vc = UIHostingController(rootView: <#your swiftUI view#>(<#your parameters #>))

you can set a modalPresentationStyle. You may have to decide which of the styles suits your needs but .currentContext prevents the dragging to dismiss.

Side note:I don't know how to dismiss a view presented from a UIHostingController though which is why I've asked a Q myself on here to find out

RyanTCB
  • 7,400
  • 5
  • 42
  • 62
0

I had a similar question here

struct Start : View {
let destinationView = SetUp()
    .navigationBarItem(title: Text("Set Up View"), titleDisplayMode: .automatic, hidesBackButton: true)

var body: some View {
    NavigationView {
        NavigationButton(destination: destinationView) {
            Text("Set Up")

        }
    }
}
}

The main thing here is that it is hiding the back button. This turns off the back button and makes it so the user can't swipe back ether.

For the setup portion of your app you could create a new SwiftUI file and add a similar thing to get home, while also incorporating your own setup code.

struct SetUp : View {

    let destinationView = Text("Your App Here")
        .navigationBarItem(title: Text("Your all set up!"), titleDisplayMode: .automatic, hidesBackButton: true)

 var body: some View {
    NavigationView {
        NavigationButton(destination: destinationView) {
            Text("Done")

        }
    }
}
}
Mason
  • 33
  • 5
0

iOS13+ solution:

This is actually very easy to do using only standard API. Wrap the contents of your sheet into NavigationView and add DragGesture on navigation view using .gesture or .simultaneousGesture. This will cancel-out internal sheet drag gesture. This method is tested and works on all iOS versions iOS13+.

iOS 13+ example:

import SwiftUI

struct SwiftUIView: View {
    @State var isPresented = false
    
    var body: some View {
        Button("Show") {
            isPresented = true
        }
        .sheet(isPresented: $isPresented) {
            NavigationView {
                VStack {
                    Text("Test View")
                }
                .navigationBarItems(
                    trailing:
                        Button {
                            isPresented = false
                        } label: {
                            Image(systemName: "xmark.circle.fill")
                                .renderingMode(.template)
                                .foregroundColor(.primary.opacity(0.5))
                        }
                )
            }
            .gesture(
                DragGesture(minimumDistance: 1, coordinateSpace: .local)
            )
        }
    }
}

Modifier to disable interactive dismiss on older systems:

extension View {
    @ViewBuilder func interactiveDismissDisabledLegacy(_ isDisabled: Bool = true) -> some View {
        if isDisabled {
            NavigationView {
                self
            }
            .gesture(
                DragGesture(minimumDistance: 1, coordinateSpace: .local)
            )
        } else {
            self
        }
    }
}

iOS15+ provides new modifier .interactiveDismissDisabled(true), which provides the same functionality. iOS 15+ example:

import SwiftUI

struct SwiftUIView: View {
    @State var isPresented = false
    
    var body: some View {
        Button("Show") {
            isPresented = true
        }
        .sheet(isPresented: $isPresented) {
            VStack {
                Text("Test View")
            }
            .interactiveDismissDisabled(true)
        }
    }
}
Mirko
  • 2,231
  • 2
  • 21
  • 17
-2

There is an extension to make controlling the modal dismission effortless, at https://gist.github.com/mobilinked/9b6086b3760bcf1e5432932dad0813c0

A temporary solution before the official solution released by Apple.

/// Example:
struct ContentView: View {
    @State private var presenting = false
    
    var body: some View {
        VStack {
            Button {
                presenting = true
            } label: {
                Text("Present")
            }
        }
        .sheet(isPresented: $presenting) {
            ModalContent()
                .allowAutoDismiss { false }
                // or
                // .allowAutoDismiss(false)
        }
    }
}
swift code
  • 115
  • 1
  • 1