SwiftUI - PresentationButton с модальным полноэкранным режимом

Я пытаюсь реализовать кнопку, которая представляет другую сцену с анимацией "Slide from Botton".

Кнопка PresentationButton выглядела как хороший кандидат, поэтому я попробовал:

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

И вот результат: enter image description here

Я хочу, чтобы зеленый вид покрывал весь экран, а также чтобы модал не "закрывался перетаскиванием".

Можно ли добавить модификатор в PresentationButton, чтобы сделать его полноэкранным, а не перетаскиваемым?

Я также попробовал навигационную кнопку, но:  - не "скользит снизу"  - Он создает "кнопку назад" в подробном представлении, которое я не хочу

Спасибо!

Ответы

Ответ 1

К сожалению, что касается Beta 2 Beta 3, это не возможно в чистом SwiftUI. Вы можете видеть, что Modal не имеет параметров для чего-либо подобного UIModalPresentationStyle.fullScreen. Аналогично для PresentationButton.

Я предлагаю подать радар.

Самое близкое, что вы можете сделать сейчас, это что-то вроде:

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

Конечно, оттуда вы можете добавить любой переход в оверлей, который вам нравится.

Ответ 2

Хотя мой другой ответ в настоящее время правильный, люди, вероятно, хотят сделать это сейчас. Мы можем использовать Environment, чтобы передать контроллер представления детям. Суть здесь

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

Добавить расширение для 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()))
       ... but then we can reset that view to include the environment
        toPresent.rootView = AnyView(
            builder()
                .environment(\.viewController, ViewControllerHolder(value: toPresent))
        )
        self.present(toPresent, animated: true, completion: nil)
    }
}

И всякий раз, когда нам это нужно, используйте его:

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

[РЕДАКТИРОВАТЬ] Хотя было бы предпочтительнее сделать что-то вроде @Environment(\.viewController) var viewController: UIViewController?, это приведет к сохранению цикла. Поэтому вам нужно использовать держатель.

Ответ 3

Так что я боролся с этим, и мне не понравилась функция наложения, а также версия в оболочке ViewController, поскольку она дала мне некоторую ошибку памяти, и я очень плохо знаком с iOS и знаю только SwiftUI и никакой UIKit.

Я разработал титры следующего с помощью только SwiftUI, что, вероятно, и делает оверлей, но для моих целей он гораздо более гибкий:

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

    var isShowing: Binding<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()
                ZStack {
                    self.content()
                    VStack {
                        HStack {
                            Spacer()
                            Button(action: {
                                withAnimation {
                                    self.isShowing.wrappedValue.toggle()
                                }
                            }, label: {
                                Text("done_button").frame(width: 200, height: 120)
                            })
                            .frame(width: 200, height: 120)
                        }
                        Spacer()
                    }
                }
                .frame(width: geometry.size.width, height: geometry.size.height)
                .animation(Animation.easeInOut(duration: 0.3))
                .background(Color.primary.colorInvert())
                .offset(x: 0, y: self.isShowing.wrappedValue ? 0 : geometry.size.height + geometry.safeAreaInsets.top)
            }
        }
    }
}

Добавление расширения к 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)
    }

}

Использование:

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

Надеюсь, это поможет!

Привет krjw

Ответ 4

Эта версия исправляет ошибку компиляции, присутствующую в XCode 11.1, а также гарантирует, что контроллер представлен в стиле, который передается.

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

Чтобы использовать эту версию, код не отличается от предыдущей версии.

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