Incluir vistas SwiftUI en la aplicación UIKit existente

Resuelto visc asked hace 5 años • 11 respuestas

¿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

visc avatar Jun 04 '19 03:06 visc
Aceptado

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 SwiftUIcomponentes en UIKitentornos existentes envolviendo a SwiftUI Viewen algo UIHostingControllerasí:

let swiftUIView = SomeSwiftUIView() // swiftUIView is View
let viewCtrl = UIHostingController(rootView: swiftUIView)

También es posible anularlo UIHostingControllery personalizarlo según las necesidades de cada uno, por ejemplo, configurándolo preferredStatusBarStylemanualmente si no funciona como SwiftUIse esperaba.

UIHostingControllerestá documentado aquí .


Usando UIKit dentro de SwiftUI

UIKitSi se debe utilizar una vista existente en un SwiftUIentorno, ¡el UIViewRepresentableprotocolo 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 SwiftUIen versiones de iOS <iOS 13, ya que SwiftUIsolo está disponible en iOS 13 y superiores. Consulte esta publicación para obtener más información.

Si desea utilizarlo SwiftUIen un proyecto con un objetivo inferior a iOS 13, debe etiquetar sus SwiftUIestructuras con @available(iOS 13.0.0, *)un atributo.

fredpi avatar Jun 03 '2019 20:06 fredpi

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

Mike Lee avatar Nov 24 '2019 05:11 Mike Lee

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 BenefitsSwiftUIViewviene el "Hola mundo" predeterminado ? Esto funciona exactamente como lo esperaba. También funciona si subclasificas .ViewSwiftUIUIHostingController

Lucas van Dongen avatar Jun 05 '2019 10:06 Lucas van Dongen

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
    }
}
Jasper Blues avatar Dec 15 '2020 23:12 Jasper Blues