web-dev-qa-db-ja.com

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に追加してフルスクリーンにしてドラッグできないようにすることはできますか?

ナビゲーションボタンも試してみましたが、-「下からスライド」しません-詳細ビューに「戻るボタン」を作成します。

ありがとう!

25
Mane Manero

残念ながら、現在 ベータ2 ベータ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)
    }

もちろん、そこからオーバーレイに好きなトランジションを追加できます。

17
arsenius

私の他の答えは現在正しいですが、人々はおそらくこれを今できるようにしたいと思うでしょう。 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()))
        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)
    }
}
_

そして、必要なときはいつでも使用してください:

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

[編集] @Environment(\.viewController) var viewController: UIViewController?のようなことをすることが望ましいですが、これは保持サイクルにつながります。したがって、ホルダーを使用する必要があります。

15
arsenius

だから私はそれに苦労していました、そしてそれは私にいくつかのメモリバグを与え、私はiOSに非常に新しいのでSwiftUIだけを知っていてUIKitを知らないので、オーバーレイ機能もViewControllerラップバージョンも好きではありませんでした。

私は credits SwiftUIを使用して以下を開発しました。これはおそらくオーバーレイが行うことですが、私の目的でははるかに柔軟性があります。

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)

                }
            }
        }
    }
}

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

}

使用法:カスタムビューを使用し、showModal変数をBinding<Bool>として渡して、ビュー自体からモーダルを閉じます。

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

2
krjw

このバージョンでは、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!")
        }
    }
}
1
Gene Z. Ragan