关闭UIHostingController中包含的SwiftUI视图

时间:2019-07-24 20:06:45

标签: ios uikit swiftui

我已将登录视图控制器重写为SwiftUI ViewSignInView包装在UIHostingController子类(final class SignInViewController: UIHostingController<SignInView> {})中,并在需要登录时以全屏方式模态显示。

一切正常,但我不知道如何从SignInViewController中撤消SignInView。我尝试添加:

@Environment(\.isPresented) var isPresented
登录成功后,在SignInView

并将其分配给false,但这似乎并不与UIKit互操作。如何关闭视图?

10 个答案:

答案 0 :(得分:5)

我最终找到了比提供的解决方案简单得多的解决方案:


final class SettingsViewController: UIHostingController<SettingsView> {
    required init?(coder: NSCoder) {
        super.init(coder: coder, rootView: SettingsView())
        rootView.dismiss = dismiss
    }

    func dismiss() {
        dismiss(animated: true, completion: nil)
    }
}

struct SettingsView: View {
    var dismiss: (() -> Void)?

    var body: some View {
        NavigationView {
            Form {
                Section {
                    Button("Dimiss", action: dismiss!)
                }
            }
            .navigationBarTitle("Settings")
        }
    }
}

答案 1 :(得分:4)

我遇到了同样的问题,多亏了这篇文章,我可以写一个混合解决方案,以提高这篇文章的解决方案的可用性:

final class RootViewController<Content: View>: UIHostingController<AnyView> {
    init(rootView: Content) {
        let dismisser = ControllerDismisser()
        let view = rootView
            .environmentObject(dismisser)

        super.init(rootView: AnyView(view))

        dismisser.host = self
    }

    @objc required dynamic init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

final class ControllerDismisser: ObservableObject {
    var host: UIHostingController<AnyView>?

    func dismiss() {
        host?.dismiss(animated: true)
    }
}

这样,我可以将此控制器初始化为普通的UIHostingController

let screen = RootViewController(rootView: MyView())

注意:我使用.environmentObject将该对象传递给需要它的视图。这样,无需将其放在初始化程序中,也无需将其传递给所有视图层次结构

答案 2 :(得分:3)

另一种方法(在我看来,相对容易些)是在您的UIViewController SwiftUI中具有view的可选属性类型,然后将其设置为将显示{{ 1}},它将包装您的UIHostingController视图。

一个简单的SettingsView:

SwiftUI

然后,当您使用struct SettingsView: View { var presentingVC: UIViewController? var body: some View { Button(action: { self.presentingVC?.presentedViewController?.dismiss(animated: true) }) { Text("Dismiss") } } } 从视图控制器呈现此视图时:

UIHostingController

现在,如您在class ViewController: UIViewController { private func presentSettingsView() { var view = SettingsView() view.presentingVC = self let hostingVC = UIHostingController(rootView: view) present(hostingVC, animated: true, completion: nil) } } 中的Button的操作中所看到的,我们将与SettingsView进行对话以消除它所呈现的视图控制器,在我们的情况下,它将是包裹ViewController的{​​{1}}。

答案 3 :(得分:2)

let rootView = SignInView();
let ctrl = UIHostingController(rootView: rootView);
ctrl.rootView.dismiss = {
    ctrl.dismiss(animated: true)
}
present(ctrl, animated:true, completion:nil);

注意:ctrl.rootView.dismiss不是rootView.dismiss

答案 4 :(得分:1)

我不确定在将来的版本中是否将isPresented连接到View的{​​{1}}。您应该submit feedback对此进行说明。

同时,请参见this answer,以了解如何从UIHostingController s访问UIViewController。

然后,您只需执行View

答案 5 :(得分:1)

我遇到一个类似的问题,提出了UIDocumentPickerViewController的实例。

在这种情况下,UIDocumentPickerViewController是模态显示的(sheet),与您的模样稍有不同-但这种方法也可能对您有用。

我可以通过遵循UIViewControllerRepresentable协议并添加回调以关闭Coordinator内部的View Controller来使其工作。

代码示例:

  

SwiftUI Beta 5

struct ContentProviderButton: View {
    @State private var isPresented = false

    var body: some View {
        Button(action: {
            self.isPresented = true
        }) {
            Image(systemName: "folder").scaledToFit()
        }.sheet(isPresented: $isPresented) { () -> DocumentPickerViewController in
            DocumentPickerViewController.init(onDismiss: {
                self.isPresented = false
            })
        }
    }
}

/// Wrapper around the `UIDocumentPickerViewController`.
struct DocumentPickerViewController {
    private let supportedTypes: [String] = ["public.image"]

    // Callback to be executed when users close the document picker.
    private let onDismiss: () -> Void

    init(onDismiss: @escaping () -> Void) {
        self.onDismiss = onDismiss
    }
}

// MARK: - UIViewControllerRepresentable

extension DocumentPickerViewController: UIViewControllerRepresentable {

    typealias UIViewControllerType = UIDocumentPickerViewController

    func makeUIViewController(context: Context) -> DocumentPickerViewController.UIViewControllerType {
        let documentPickerController = UIDocumentPickerViewController(documentTypes: supportedTypes, in: .import)
        documentPickerController.allowsMultipleSelection = true
        documentPickerController.delegate = context.coordinator
        return documentPickerController
    }

    func updateUIViewController(_ uiViewController: DocumentPickerViewController.UIViewControllerType, context: Context) {}

    // MARK: Coordinator

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, UIDocumentPickerDelegate {
        var parent: DocumentPickerViewController

        init(_ documentPickerController: DocumentPickerViewController) {
            parent = documentPickerController
        }

        func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
            // TODO: handle user selection
        }

        func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
            parent.onDismiss()
        }
    }
}

答案 6 :(得分:1)

您可以只使用通知。

Swift 5.1

在SwiftUI按钮处理程序中:

NotificationCenter.default.post(name: NSNotification.Name("dismissSwiftUI"), object: nil)

在UIKit视图控制器中:

NotificationCenter.default.addObserver(forName: NSNotification.Name("dismissSwiftUI"), object: nil, queue: nil) { (_) in
    hostingVC.dismiss(animated: true, completion: nil)
}

答案 7 :(得分:1)

如何通过托管控制器演示者扩展环境值?从层次结构的任何角度看,它都可以像// Declaro variables para después de los 5 minutos. let sec= 0, min = 0, hour = 0; // Para contar hasta los 5 minutos let startMin = 5*60, cronometro = 0; cronometro = setInterval(function (){ if(cronometro >= startMin){ sec++; if(sec == 60){ min++; sec = 0; if(min == 60){ hour++; min = 0; } } } minutos.innerHTML = min }, 1000); 一样使用,并且易于重用和可伸缩。 定义新的环境值:

presentationMode

然后在需要时传递值,例如:

struct UIHostingControllerPresenter {
    init(_ hostingControllerPresenter: UIViewController) {
        self.hostingControllerPresenter = hostingControllerPresenter
    }
    private unowned var hostingControllerPresenter: UIViewController
    func dismiss() {
        hostingControllerPresenter.dismiss(animated: true, completion: nil)
    }
}

private enum UIHostingControllerPresenterEnvironmentKey: EnvironmentKey {
    static let defaultValue: UIHostingControllerPresenter? = nil
}

extension EnvironmentValues {
    /// An environment value that attempts to extend `presentationMode` for case where
    /// view is presented via `UIHostingController` so dismissal through
    /// `presentationMode` doesn't work.
    var uiHostingControllerPresenter: UIHostingControllerPresenter? {
        get { self[UIHostingControllerPresenterEnvironmentKey.self] }
        set { self[UIHostingControllerPresenterEnvironmentKey.self] = newValue }
    }
}

并喜欢使用

let view = AnySwiftUIView().environment(\.uiHostingControllerPresenter, UIHostingControllerPresenter(self))
let viewController = UIHostingController(rootView: view)
present(viewController, animated: true, completion: nil)
...

否则你会去的地方

@Environment(\.uiHostingControllerPresenter) private var uiHostingControllerPresenter
...
uiHostingControllerPresenter?.dismiss()

答案 8 :(得分:0)

这里提供的所有答案都不适用于我,这可能是因为参考文献薄弱。这是我想出的解决方案:

创建视图和UIHostingController:

let delegate = SheetDismisserProtocol()
let signInView = SignInView(delegate: delegate)
let host = UIHostingController(rootView: AnyView(signInView))
delegate.host = host
// Present the host modally 

SheetDismisserProtocol:

class SheetDismisserProtocol: ObservableObject {
    var host: UIHostingController<AnyView>? = nil

    func dismiss() {
        host?.dismiss(animated: true)
    }
}

必须关闭的视图:

struct SignInView: View {
    @ObservedObject var delegate: SheetDismisserProtocol

    var body: some View {
        Button(action: {
            self.delegate.dismiss()
        })
    }
}

答案 9 :(得分:0)

我发现了另一种似乎行之有效的方法,并且感觉比其他一些方法更干净。步骤:

  1. 将一个dismissAction属性添加到SwiftUI视图:
struct SettingsUIView: View {
    var dismissAction: (() -> Void)
    ...
}    
  1. 要关闭视图时调用dismissAction
Button(action: dismissAction ) {
    Text("Done")
}
  1. 呈现视图时,为其提供解雇处理程序:
let settingsView = SettingsUIView(dismissAction: {self.dismiss( animated: true, completion: nil )})
let settingsViewController = UIHostingController(rootView: settingsView )

present( settingsViewController, animated: true )