29

我正在尝试实现一个按钮,该按钮使用“从底部滑动”动画呈现另一个场景。

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

结果如下: 在此处输入图像描述

我希望绿色视图覆盖整个屏幕,并且模式不能“拖动关闭”。

是否可以向 PresentationButton 添加修饰符以使其全屏显示且不可拖动?

我也尝试过导航按钮,但是: - 它不会“从底部滑动” - 它会在详细视图上创建一个“后退按钮”,这是我不想要的

谢谢!

4

6 回答 6

24

不幸的是,截至测试版 2Beta 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)
    }

当然,您可以从那里在叠加层中添加您喜欢的任何过渡。

于 2019-07-02T01:06:29.330 回答
19

尽管我的其他答案目前是正确的,但人们现在可能希望能够做到这一点。我们可以使用 将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?的事情会导致保留周期。因此,您需要使用支架。

于 2019-07-04T04:26:40.243 回答
12

Xcode 12.0 - SwiftUI 2 - iOS 14

现在可以了。使用 fullScreenCover() 修饰符。

var body: some View {
    Button("Present!") {
        self.isPresented.toggle()
    }
    .fullScreenCover(isPresented: $isPresented, content: FullScreenModalView.init)
}

使用 Swift 进行黑客攻击

于 2020-07-14T18:31:36.583 回答
3

我对此的解决方案(您可以轻松地扩展它以允许调整呈现的工作表上的其他参数)只是将 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)
    }

}

在场景委托中使用 HSHostingController 而不是 UIHostingController,如下所示:

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

然后在触发工作表之前告诉 HSHostingControllerParams 类你想要什么演示风格

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

通过类单例传递参数感觉有点“肮脏”,但在实践中 - 您必须创建一个非常模糊的场景才能使其无法按预期工作。

您可能会弄乱环境变量等(正如其他答案所做的那样) - 但对我来说,增加的复杂性不值得纯粹。

更新:有关具有附加功能的扩展解决方案,请参阅此要点

于 2020-04-09T09:55:55.640 回答
2

此版本修复了 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!")
        }
    }
}
于 2019-10-21T21:09:05.077 回答
2

所以我为此苦苦挣扎,我不喜欢覆盖功能和 ViewController 包装版本,因为它给了我一些内存错误,而且我对 iOS 很陌生,只知道 SwiftUI 而没有 UIKit。

我仅使用 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变量作为 a传递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

于 2019-10-18T08:56:06.780 回答