Incluir vistas SwiftUI en la aplicación UIKit existente
¿Es posible crear vistas con SwiftUI al lado de una aplicación UIKit existente?
Tengo una aplicación existente escrita en Objective-C. Comencé a migrar a Swift 5. Me pregunto si puedo usar SwiftUI junto con mis vistas UIKit .xib existentes.
Es decir, quiero algunas vistas creadas con SwiftUI y otras vistas creadas con UIKit en la misma aplicación. Sin mezclar los dos, por supuesto.
SomeObjCSwiftProject/
SwiftUIViewController.swift
SwiftUIView.xib
UIKitViewController.swift
UIKitView.xib
Trabajando juntos
edición 06/05/19: Se agregó información sobre UIHostingController según lo sugerido por @Departamento B en su respuesta. ¡Los créditos son para él!
Usando SwiftUI dentro de UIKit
Se pueden usar SwiftUI
componentes en UIKit
entornos existentes envolviendo a SwiftUI
View
en algo UIHostingController
así:
let swiftUIView = SomeSwiftUIView() // swiftUIView is View
let viewCtrl = UIHostingController(rootView: swiftUIView)
También es posible anularlo UIHostingController
y personalizarlo según las necesidades de cada uno, por ejemplo, configurándolo preferredStatusBarStyle
manualmente si no funciona como SwiftUI
se esperaba.
UIHostingController
está documentado aquí .
Usando UIKit dentro de SwiftUI
UIKit
Si se debe utilizar una vista existente en un SwiftUI
entorno, ¡el UIViewRepresentable
protocolo está ahí para ayudar! Está documentado aquí y se puede ver en acción en este tutorial oficial de Apple.
Compatibilidad
Tenga en cuenta que no puede utilizarlo SwiftUI
en versiones de iOS <iOS 13, ya que SwiftUI
solo está disponible en iOS 13 y superiores. Consulte esta publicación para obtener más información.
Si desea utilizarlo SwiftUI
en un proyecto con un objetivo inferior a iOS 13, debe etiquetar sus SwiftUI
estructuras con @available(iOS 13.0.0, *)
un atributo.
Si desea incrustar SwiftUI en un controlador de vista UIKit, use una vista de contenedor.
class ViewController: UIViewController {
@IBOutlet weak var theContainer: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let childView = UIHostingController(rootView: SwiftUIView())
addChild(childView)
childView.view.frame = theContainer.bounds
theContainer.addSubview(childView.view)
childView.didMove(toParent: self)
}
}
Referencia
Controlador UIHosting
Aunque de momento no se ha escrito la documentación para la clase, UIHostingController<Content>
parece ser lo que estás buscando: https://developer.apple.com/documentation/swiftui/uihostingcontroller
Lo acabo de probar en mi aplicación con la siguiente línea de código:
let vc = UIHostingController(rootView: BenefitsSwiftUIView())
¿ De dónde BenefitsSwiftUIView
viene el "Hola mundo" predeterminado ? Esto funciona exactamente como lo esperaba. También funciona si subclasificas .View
SwiftUI
UIHostingController
Así es como lo hago:
Crear un adaptador SwiftUI
/**
* Adapts a SwiftUI view for use inside a UIViewController.
*/
class SwiftUIAdapter<Content> where Content : View {
private(set) var view: Content!
weak private(set) var parent: UIViewController!
private(set) var uiView : WrappedView
private var hostingController: UIHostingController<Content>
init(view: Content, parent: UIViewController) {
self.view = view
self.parent = parent
hostingController = UIHostingController(rootView: view)
parent.addChild(hostingController)
hostingController.didMove(toParent: parent)
uiView = WrappedView(view: hostingController.view)
}
deinit {
hostingController.removeFromParent()
hostingController.didMove(toParent: nil)
}
}
Agregue a un controlador de vista de la siguiente manera:
class FeedViewController: UIViewController {
var adapter : SwiftUIAdapter<FeedView>!
override required init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
adapter = SwiftUIAdapter(view: FeedView(), parent: self)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
/** Override load view to load the SwiftUI adapted view */
override func loadView() {
view = adapter.uiView;
}
}
Aquí está el código para la vista ajustada
La vista ajustada utiliza el diseño manual (layoutSubViews) en lugar del diseño automático, para este caso tan simple.
class WrappedView: UIView {
private (set) var view: UIView!
init(view: UIView) {
self.view = view
super.init(frame: CGRect.zero)
addSubview(view)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func layoutSubviews() {
super.layoutSubviews()
view.frame = bounds
}
}