关闭包含在 UIHostingController 中的 SwiftUI 视图

jja*_*tie 36 uikit ios swiftui

我已经将我的登录视图控制器重写为 SwiftUI View。该SignInView被包裹在一UIHostingController子类中(final class SignInViewController: UIHostingController<SignInView> {}),并且被呈现模态,全屏,当符号是必要英寸

一切正常,除了我不知道如何SignInViewControllerSignInView. 我试过添加:

@Environment(\.isPresented) var isPresented
Run Code Online (Sandbox Code Playgroud)

SignInView并将其分配给false当登录成功,但这并没有出现与UIKit的互操作到。我怎样才能关闭视图?

Sea*_*ins 37

我发现了另一种似乎效果很好的方法,并且感觉比其他一些方法更简洁。脚步:

  1. dismissAction向 SwiftUI 视图添加一个属性:
struct SettingsUIView: View {
    var dismissAction: (() -> Void)
    ...
}    
Run Code Online (Sandbox Code Playgroud)
  1. dismissAction当你想关闭视图时调用:
Button(action: dismissAction ) {
    Text("Done")
}
Run Code Online (Sandbox Code Playgroud)
  1. 当您呈现视图时,为其提供一个解除处理程序:
let settingsView = SettingsUIView(dismissAction: {self.dismiss( animated: true, completion: nil )})
let settingsViewController = UIHostingController(rootView: settingsView )

present( settingsViewController, animated: true )
Run Code Online (Sandbox Code Playgroud)

  • 使用这种方法,您将无法关闭“settingsViewController”,只能关闭父视图控制器,因为它在“dismissAction”中使用“self”。 (4认同)
  • 我不确定最佳实践是什么,但我真的很喜欢这个答案,向下滚动寻找答案的人会很高兴他们这样做了。在我的使用中,我已将dismissAction重命名为onComplete,并且它需要一个可选错误。这使得它与我喜欢处理可能成功或失败的对话框的方式非常相似,并允许演示主机关闭它并适当地处理结果。 (2认同)

jja*_*tie 36

更新:来自 iOS 15 beta 1 的发行说明:

isPresented、PresentationMode 和新的 DismissAction 操作会关闭从 UIKit 呈现的托管控制器。(52556186)


我最终找到了一个比提供的更简单的解决方案:


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")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 为什么将关闭关闭设为可选,然后强制打开它...... (2认同)
  • @jjatie 由于需要“NSCoder”,因此如何创建“SettingsViewController”? (2认同)

bon*_*onk 11

此处提供的所有答案对我都不起作用,可能是因为参考资料较弱。这是我想出的解决方案:

创建视图和 UIHostingController:

let delegate = SheetDismisserProtocol()
let signInView = SignInView(delegate: delegate)
let host = UIHostingController(rootView: AnyView(signInView))
delegate.host = host
// Present the host modally 
Run Code Online (Sandbox Code Playgroud)

SheetDismisserProtocol:

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

    func dismiss() {
        host?.dismiss(animated: true)
    }
}
Run Code Online (Sandbox Code Playgroud)

必须驳回的观点:

struct SignInView: View {
    @ObservedObject var delegate: SheetDismisserProtocol

    var body: some View {
        Button(action: {
            self.delegate.dismiss()
        })
    }
}
Run Code Online (Sandbox Code Playgroud)

  • “SheetDismisserProtocol”中的“host”应该是“weak”,否则“UIHostingController”实例将会泄漏。 (3认同)

Jay*_*win 9

你可以只使用通知。

斯威夫特 5.1

在 SwiftUI 按钮处理程序中:

NotificationCenter.default.post(name: NSNotification.Name("dismissSwiftUI"), object: nil)
Run Code Online (Sandbox Code Playgroud)

在 UIKit 视图控制器中:

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

  • 请不要在这种情况下使用通知中心。您很容易忘记您观察到的通知。这是一种将信息从一个类广播到注册侦听器的简单机制,并且非常适合系统范围的通知,而不是像这样的特定实现。 (2认同)

joh*_*n07 7

let rootView = SignInView();
let ctrl = UIHostingController(rootView: rootView);
ctrl.rootView.dismiss = {[weak ctrl] in
    ctrl?.dismiss(animated: true)
}
present(ctrl, animated:true, completion:nil);
Run Code Online (Sandbox Code Playgroud)

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


emr*_*pun 6

另一种方法(在我看来相对更容易)是UIViewController在您的中有一个可选的属性类型,SwiftUI view然后将其设置为将呈现UIHostingControllerSwiftUI视图控制器,它将包装您的视图。

一个简单的设置视图:

struct SettingsView: View {
    
    var presentingVC: UIViewController?
    
    var body: some View {
        Button(action: {
            self.presentingVC?.presentedViewController?.dismiss(animated: true)
        }) {
            Text("Dismiss")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,当您使用UIHostingController以下方法从视图控制器呈现此视图时:

class ViewController: UIViewController {

    private func presentSettingsView() {
        var view = SettingsView()
        view.presentingVC = self
        let hostingVC = UIHostingController(rootView: view)
        present(hostingVC, animated: true, completion: nil)
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,你可以在的作用看ButtonSettingsView,我们要谈ViewController驳回它呈现视图控制器,这对我们来说将是UIHostingController一个包装SettingsView


Sta*_*ida 5

使用主机控制器演示器扩展环境值怎么样?它允许presentationMode在层次结构中的任何视图中使用,并且易于重用和可扩展。定义您的新环境值:

struct UIHostingControllerPresenter {
    init(_ hostingControllerPresenter: UIViewController) {
        self.hostingControllerPresenter = hostingControllerPresenter
    }
    private unowned var hostingControllerPresenter: UIViewController
    func dismiss() {
        if let presentedViewController = hostingControllerPresenter.presentedViewController, !presentedViewController.isBeingDismissed { // otherwise an ancestor dismisses hostingControllerPresenter - which we don't want.
            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 }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

并享受使用

@Environment(\.uiHostingControllerPresenter) private var uiHostingControllerPresenter
...
uiHostingControllerPresenter?.dismiss()
Run Code Online (Sandbox Code Playgroud)

你去的地方

@Environment(\.presentationMode) private var presentationMode
...
presentationMode.wrappedValue.dismiss() // .isPresented = false
Run Code Online (Sandbox Code Playgroud)