SwiftUI - PresentationButton with modal that is full screen

swiftui full screen view
swiftui modal
swiftui modal github
swiftui sheet half screen
macos swiftui modal
present modal view controller not full screen
presentationbutton swiftui
storyboard segue full screen

I am trying to implement a button that presents another scene with a "Slide from Botton" animation.

PresentationButton looked like a good candidate, so I gave it a try:

import SwiftUI

struct ContentView : View {
    var body: some View {
        NavigationView {
            PresentationButton(destination: Green().frame(width: 1000.0)) {
                Text("Click")

                }.navigationBarTitle(Text("Navigation"))
        }
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
                .previewDevice("iPhone X")
                .colorScheme(.dark)

            ContentView()
                .colorScheme(.dark)
                .previewDevice("iPad Pro (12.9-inch) (3rd generation)"

            )

        }

    }
}
#endif

And here is the result:

I want the green view to cover the whole screen, and also the modal to be not "draggable to close".

Is it possible to add modifier to PresentationButton to make it full screen, and not draggable?

I have also tried a Navigation Button, but: - It doesn't "slide from bottom" - It creates a "back button" on detail view, which I don't want

thanks!

Unfortunately, as of Beta 2 Beta 3, this is not possible in pure SwiftUI. You can see that Modal has no parameters for anything like UIModalPresentationStyle.fullScreen. Likewise for PresentationButton.

I suggest filing a radar.

The nearest you can currently do is something like:

    @State var showModal: Bool = false
    var body: some View {
        NavigationView {
            Button(action: {
                self.showModal = true
            }) {
                Text("Tap me!")
            }
        }
        .navigationBarTitle(Text("Navigation!"))
        .overlay(self.showModal ? Color.green : nil)
    }

Of course, from there you can add whatever transition you like in the overlay.

Full-screen modal and circular reveal links in SwiftUI, However, there are situations where we might want to keep the old full-screen version. Whereas UIKit supports both kinds of modals, any modal  SwiftUI’s fullScreenCover() modifier gives us a presentation style for times when you want to cover as much of the screen as possible, and in code it works almost identically to regular sheets. For example, this will present a full screen modal view when the button is pressed: struct ContentView

Although my other answer is currently correct, people probably want to be able to do this now. We can use the Environment to pass a view controller to children. Gist here

struct ViewControllerHolder {
    weak var value: UIViewController?
}


struct ViewControllerKey: EnvironmentKey {
    static var defaultValue: ViewControllerHolder { return ViewControllerHolder(value: UIApplication.shared.windows.first?.rootViewController ) }
}

extension EnvironmentValues {
    var viewController: UIViewControllerHolder {
        get { return self[ViewControllerKey.self] }
        set { self[ViewControllerKey.self] = newValue }
    }
}

Add an extension to UIViewController

extension UIViewController {
    func present<Content: View>(style: UIModalPresentationStyle = .automatic, @ViewBuilder builder: () -> Content) {
        // Must instantiate HostingController with some sort of view...
        let toPresent = UIHostingController(rootView: AnyView(EmptyView()))
        toPresent.modalPresentationStyle = style
        // ... but then we can reset rootView to include the environment
        toPresent.rootView = AnyView(
            builder()
                .environment(\.viewController, ViewControllerHolder(value: toPresent))
        )
        self.present(toPresent, animated: true, completion: nil)
    }
}

And whenever we need it, use it:

struct MyView: View {

    @Environment(\.viewController) private var viewControllerHolder: ViewControllerHolder
    private var viewController: UIViewController? {
        self.viewControllerHolder.value
    }

    var body: some View {
        Button(action: {
           self.viewController?.present(style: .fullScreen) {
              MyView()
           }
        }) {
           Text("Present me!")
        }
    }
}

[EDIT] Although it would be preferable to do something like @Environment(\.viewController) var viewController: UIViewController? this leads to a retain cycle. Therefore, you need to use the holder.

SwiftUI Fullscreen Modal Hack withAnimation, Something missing in SwiftUI right now is the ability to present a modal fullscreen​. With a Duration: 14:14 Posted: Jan 2, 2020 However, there are situations where we might want to keep the old full-screen version. Whereas UIKit supports both kinds of modals, any modal navigation in SwiftUI is interactive: In this tutorial…

So I was struggling with that and I didn't like the overlay feature nor the ViewController wrapped version since it gave me some memory bug and I am very new to iOS and only know SwiftUI and no UIKit.

I developed credits the following with just SwiftUI which is probably what an overlay does but for my purposes it is much more flexible:

struct FullscreenModalView<Presenting, Content>: View where Presenting: View, Content: View {

    @Binding var isShowing: Bool
    let parent: () -> Presenting
    let content: () -> Content

    @inlinable public init(isShowing: Binding<Bool>, parent: @escaping () -> Presenting, @ViewBuilder content: @escaping () -> Content) {
        self._isShowing = isShowing
        self.parent = parent
        self.content = content
    }

    var body: some View {
        GeometryReader { geometry in
            ZStack {
                self.parent().zIndex(0)
                if self.$isShowing.wrappedValue {
                    self.content()
                    .background(Color.primary.colorInvert())
                    .edgesIgnoringSafeArea(.all)
                    .frame(width: geometry.size.width, height: geometry.size.height)
                    .transition(.move(edge: .bottom))
                    .zIndex(1)

                }
            }
        }
    }
}

Adding an extension to View:

extension View {

    func modal<Content>(isShowing: Binding<Bool>, @ViewBuilder content: @escaping () -> Content) -> some View where Content: View {
        FullscreenModalView(isShowing: isShowing, parent: { self }, content: content)
    }

}

Usage: Use a custom view and pass the showModal variable as a Binding<Bool> to dismiss the modal from the view itself.

struct ContentView : View {
    @State private var showModal: Bool = false
    var body: some View {
        ZStack {
            Button(action: {
                withAnimation {
                    self.showModal.toggle()
                }
            }, label: {
                HStack{
                   Image(systemName: "eye.fill")
                    Text("Calibrate")
                }
               .frame(width: 220, height: 120)
            })
        }
        .modal(isShowing: self.$showModal, content: {
            Text("Hallo")
        })
    }
}

I hope this helps!

Greetings krjw

User arsenius, I want the green view to cover the whole screen, and also the modal to be not "​draggable to close". Is it possible to add modifier to PresentationButton to make it​  My goal is to have custom modals present over an root view that is essentially a tabbed view. So, I wrapped the TabView in a ZStack and am using an ObservableOBject. But I don't feel I'm doing it the

This version fixes the compile error present in XCode 11.1 as well as ensures that controller is presented in the style that is passed in.

import SwiftUI

struct ViewControllerHolder {
    weak var value: UIViewController?
}

struct ViewControllerKey: EnvironmentKey {
    static var defaultValue: ViewControllerHolder {
        return ViewControllerHolder(value: UIApplication.shared.windows.first?.rootViewController)

    }
}

extension EnvironmentValues {
    var viewController: UIViewController? {
        get { return self[ViewControllerKey.self].value }
        set { self[ViewControllerKey.self].value = newValue }
    }
}

extension UIViewController {
    func present<Content: View>(style: UIModalPresentationStyle = .automatic, @ViewBuilder builder: () -> Content) {
        let toPresent = UIHostingController(rootView: AnyView(EmptyView()))
        toPresent.modalPresentationStyle = style
        toPresent.rootView = AnyView(
            builder()
                .environment(\.viewController, toPresent)
        )
        self.present(toPresent, animated: true, completion: nil)
    }
}

To use this version, the code is unchanged from the previous version.

struct MyView: View {

    @Environment(\.viewController) private var viewControllerHolder: UIViewController?
    private var viewController: UIViewController? {
        self.viewControllerHolder.value
    }

    var body: some View {
        Button(action: {
           self.viewController?.present(style: .fullScreen) {
              MyView()
           }
        }) {
           Text("Present me!")
        }
    }
}

User arsenius, 19 SwiftUI update navigation bar title color Oct 17. 9 SwiftUI - PresentationButton with modal that is full screen Jul 4. 9 SwiftUI - PresentationButton with modal  SwiftUI has some presentation modifiers to display alerts, sheets and full screen views. On one hand, presenting an alert is nice and easy, it feels really well designed. But on the other hand presenting a modal screen (or action sheets or popovers) is too cumbersome.

My solution for this (which you can easily extend to allow other params on the presented sheets to be tweaked) is to just subclass UIHostingController

//HSHostingController.swift

import Foundation
import SwiftUI

class HSHostingControllerParams {
    static var nextModalPresentationStyle:UIModalPresentationStyle?
}

class HSHostingController<Content> : UIHostingController<Content> where Content : View {

    override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {

        if let nextStyle = HSHostingControllerParams.nextModalPresentationStyle {
            viewControllerToPresent.modalPresentationStyle = nextStyle
            HSHostingControllerParams.nextModalPresentationStyle = nil
        }

        super.present(viewControllerToPresent, animated: flag, completion: completion)
    }

}

use HSHostingController instead of UIHostingController in your scene delegate like so:

    // Use a HSHostingController as window root view controller.
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)

        //This is the only change from the standard boilerplate
        window.rootViewController = HSHostingController(rootView: contentView)

        self.window = window
        window.makeKeyAndVisible()
    }

then just tell the HSHostingControllerParams class what presentation style you want before triggering a sheet

        .navigationBarItems(trailing:
            HStack {
                Button("About") {
                    HSHostingControllerParams.nextModalPresentationStyle = .fullScreen
                    self.showMenuSheet.toggle()
                }
            }
        )

Passing the params via the class singleton feels a little 'dirty', but in practice - you would have to create a pretty obscure scenario for this not to work as expected.

You could mess around with environment variables and the like (as other answers have done) - but to me, the added complication isn't worth the purity.

update: see this gist for extended solution with additional capabilities

swiftui, Use SwiftUI GeometryReader without affecting outer frame? swiftui asked Jun 10 SwiftUI - PresentationButton with modal that is full screen · stackoverflow.com. swiftUI you can use only 2 option using NavigateButton (push) or present with PresentationButton – Anbu.Karthik Jul 1 '19 at 4:31 then, is there a way to hide arrow when using NavigateButton, i just want to show "See More" text, tapping on it should push to next screen? – keshav Jul 1 '19 at 4:37

User arsenius, SwiftUI - PresentationButton con modal que es pantalla completa. Estoy tratando import SwiftUI struct ContentView : View { var body: some View fullScreen . Modal Sheets. A custom modal sheet is a full-screen view that slides over your app’s current screen. Use a modal sheet when you need to give people a way to perform an important custom task in a mode that’s separate from their current context.

Fucking SwiftUI, 42 SwiftUI update navigation bar title color · 22 How to access IBOutlets declared in superclass? 13 SwiftUI - PresentationButton with modal that is full screen. Modals in iOS 13 are not full screen anymore and allow you to use swipe-to-dismiss. Apple added a new property isModalInPresentation to UIViewController which can be set to true if you want to prevent the user from dismissing your modal using a swipe gesture. However this functionality is currently not exposed in SwiftUI.

All the answers you found here don't mean to be complete or detail, the purpose here is to act as a cheat sheet or a place that you can pick up keywords you can  Language Generic Structure Navigation Link. Since SwiftUI is using a regular UINavigationController behind the scenes, the view controller will still have a valid. SwiftUI has some presentation modifiers to display alerts, sheets and full screen views. For SwiftUI, wrapping the List view in a NavigationView is also very easy.

Comments
  • Is there an error in this code? Because I've tried using your gist on Beta 6, and at the line self.viewControllerHolder.value inside MyView the compiler gives the error Ambiguous reference to member 'value(forKey:)', so I've tried using self.viewControllerHolder?.value(forKey: "") as? UIViewController and it crashes with error valueForKey:]: this class is not key value coding-compliant for the key .'
  • Figured it out: just remove .value from self.viewControllerHolder.value. In Beta 7 however, this translates to a standard .sheet modal, so it's not really full screen
  • I suggest to add toPresent.modalPresentationStyle = style inside the present function of the extension to make the newly presented screen actually full screen
  • To dismiss the ViewController I pass it to my View and then call self.vc?.dismiss(animated: true, completion: {}) but I have the suspicion that this still keeps something in memory because when I call this again and then trigger an update on a Publisher and catch it via onReceive() it gets called as often as I've opened and dismissed the ViewController any thoughts?
  • @arsenius Any update on this code for the month of December 2019? I'm getting a "Cannot convert value of type 'Environment<ViewControllerHolder>' to specified type 'UIViewController?'" for @Environment(\.viewController) private var viewControllerHolder: UIViewController?
  • It has limitations you must place it on top view, not on views nested in view hierarchies so it works differently then sheet
  • Do you mean this or do you mean .sheet?
  • This if you attach this modifier to button nested in view nested in andother view then it is centered in center of this button or subview. Maybe it be possible to somehow set frame, position explicitly based on UIScreen size
  • I think calculating difference between screen center and parent center and setting it as offset could help
  • I tried this code but it doesn't work. Initially you get an error Value of optional type 'UIViewController?' must be unwrapped to refer to member 'value' of wrapped base type 'UIViewController' but then force-unwrapping gives another error: Ambiguous reference to member 'value(forKey:)' - so you need to change self.viewControllerHolder.value to self.viewControllerHolder!