我已将登录视图控制器重写为SwiftUI View
。 SignInView
包装在UIHostingController
子类(final class SignInViewController: UIHostingController<SignInView> {}
)中,并在需要登录时以全屏方式模态显示。
一切正常,但我不知道如何从SignInViewController
中撤消SignInView
。我尝试添加:
@Environment(\.isPresented) var isPresented
登录成功后,在SignInView
中并将其分配给false
,但这似乎并不与UIKit互操作。如何关闭视图?
答案 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)
我发现了另一种似乎行之有效的方法,并且感觉比其他一些方法更干净。步骤:
dismissAction
属性添加到SwiftUI视图:struct SettingsUIView: View {
var dismissAction: (() -> Void)
...
}
dismissAction
:Button(action: dismissAction ) {
Text("Done")
}
let settingsView = SettingsUIView(dismissAction: {self.dismiss( animated: true, completion: nil )})
let settingsViewController = UIHostingController(rootView: settingsView )
present( settingsViewController, animated: true )