web-dev-qa-db-fra.com

SwiftUI - PresentationButton avec modal plein écran

J'essaie d'implémenter un bouton qui présente une autre scène avec une animation "Slide from Botton".

PresentationButton ressemblait à un bon candidat, j'ai donc essayé:

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

Et voici le résultat: enter image description here

Je veux que la vue verte couvre tout l'écran et que le modal ne soit pas "déplaçable pour fermer".

Est-il possible d'ajouter un modificateur à PresentationButton pour le rendre plein écran et non glissable?

J'ai également essayé un bouton de navigation, mais: - il ne "glisse pas du bas" - il crée un "bouton retour" sur la vue détaillée, ce que je ne veux pas

merci!

25
Mane Manero

Malheureusement, au Bêta 2 Bêta 3, ce n'est pas possible en SwiftUI pur. Vous pouvez voir que Modaln'a pas de paramètres pour quelque chose comme UIModalPresentationStyle.fullScreen. De même pour PresentationButton .

Je suggère de déposer un radar.

Le plus proche que vous pouvez actuellement faire est quelque chose comme:

    @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)
    }

Bien sûr, à partir de là, vous pouvez ajouter la transition que vous souhaitez dans la superposition.

17
arsenius

Bien que mon autre réponse soit actuellement correcte, les gens veulent probablement pouvoir le faire maintenant. Nous pouvons utiliser le Environment pour passer un contrôleur de vue aux enfants. Gist ici

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 }
    }
}

Ajouter une extension à 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)
    }
}

Et chaque fois que nous en avons besoin, utilisez-le:

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] Bien qu'il soit préférable de faire quelque chose comme @Environment(\.viewController) var viewController: UIViewController? cela conduit à un cycle de rétention. Par conséquent, vous devez utiliser le support.

15
arsenius

Je luttais donc avec cela et je n'aimais pas la fonction de superposition ni la version enveloppée de ViewController car cela me donnait un bug de mémoire et je suis très nouveau sur iOS et je ne connais que SwiftUI et pas UIKit.

J'ai développé crédits ce qui suit avec juste SwiftUI qui est probablement ce que fait une superposition mais pour mes besoins, elle est beaucoup plus 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)

                }
            }
        }
    }
}

Ajout d'une extension à 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)
    }

}

Utilisation: utilisez une vue personnalisée et passez la variable showModal en tant que Binding<Bool> pour supprimer le modal de la vue elle-même.

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")
        })
    }
}

J'espère que ça aide!

Salutations krjw

2
krjw

Cette version corrige l'erreur de compilation présente dans XCode 11.1 et garantit que le contrôleur est présenté dans le style transmis.

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)
    }
}

Pour utiliser cette version, le code est inchangé par rapport à la version précédente.

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!")
        }
    }
}
1
Gene Z. Ragan