web-dev-qa-db-fra.com

Inclure des vues SwiftUI dans l'application UIKit existante

Est-il possible de créer des vues avec SwiftUI côte à côte avec une application UIKit existante?

J'ai une application existante écrite en Objective-C. J'ai commencé la migration vers Swift 5. Je me demande si je peux utiliser SwiftUI avec mes vues UIKit .xib existantes.

C'est-à-dire que je veux des vues construites avec SwiftUI et d'autres vues construites avec UIKit dans la même application. Ne pas mélanger les deux bien sûr.

SomeObjCSwiftProject/
    SwiftUIViewController.Swift
    SwiftUIView.xib
    UIKitViewController.Swift
    UIKitView.xib

Travailler côte à côte

49
visc

edit 05/06/19: Ajout d'informations sur UIHostingController comme suggéré par @Departamento B dans sa réponse. Les crédits vont à lui !


Utilisation de SwiftUI avec UIKit

On peut utiliser des composants SwiftUI dans des environnements UIKit existants en enveloppant un SwiftUIView dans un UIHostingController comme ceci:

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

Il est également possible de remplacer UIHostingController et de le personnaliser selon ses besoins, e. g. en définissant le preferredStatusBarStyle manuellement s'il ne fonctionne pas via SwiftUI comme prévu.

UIHostingController est documenté ici .


Utiliser UIKit avec SwiftUI

Si une vue UIKit existante doit être utilisée dans un environnement SwiftUI, le protocole UIViewRepresentable est là pour vous aider! Il est documenté ici et peut être vu en action dans this official Apple tutorial.


Compatibilité

Veuillez noter que les composants UIKit et SwiftUI ne peuvent être utilisés conjointement que si l'application cible iOS 13+, car SwiftUI n'est disponible que sur iOS 13+. Voir this post pour plus d'informations.

49
fredpi

UIHostingController

Bien qu'à l'heure actuelle la documentation de la classe n'ait pas été écrite, UIHostingController<Content> semble être ce que vous recherchez: https://developer.Apple.com/documentation/swiftui/uihostingcontroller

Je viens de l'essayer dans mon application avec la ligne de code suivante:

let vc = UIHostingController(rootView: BenefitsSwiftUIView())

BenefitsSwiftUIView est juste le "Hello World" par défaut View de SwiftUI. Cela fonctionne exactement comme vous vous y attendez. Cela fonctionne également si vous sous-classe UIHostingController.

14
Departamento B

Si vous souhaitez intégrer SwiftUI dans un contrôleur de vue UIKit, utilisez une vue conteneur.

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)
    }
}

Référence

10
Mike Lee

Un élément que je n'ai pas encore vu mentionné, et concerne Xcode 11 beta 5 (11M382q), implique la mise à jour du fichier info.plist de votre application.

Pour mon scénario, je prends une application existante Swift & UIKit et je la migre entièrement pour devenir une application iOS 13 et SwiftUI pure, donc la compatibilité descendante n'est pas un problème pour moi.

Après avoir apporté les modifications nécessaires à AppDelegate:

// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication,
                 configurationForConnecting connectingSceneSession: UISceneSession,
                 options: UIScene.ConnectionOptions) -> UISceneConfiguration {
    return UISceneConfiguration(name: "Default Configuration",
                                sessionRole: connectingSceneSession.role)
}

Et en ajoutant une classe SceneDelegate:

import UIKit
import SwiftUI

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: HomeList())
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}

Je rencontrais un problème où mon SceneDelegate n'était pas appelé. Ce problème a été résolu en ajoutant les éléments suivants dans mon fichier info.plist:

<key>UIApplicationSceneManifest</key>
<dict>
    <key>UIApplicationSupportsMultipleScenes</key>
    <false/>
    <key>UISceneConfigurations</key>
    <dict>
        <key>UIWindowSceneSessionRoleApplication</key>
        <array>
            <dict>
                <key>UISceneClassName</key>
                <string></string>
                <key>UISceneDelegateClassName</key>
                <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
                <key>UISceneConfigurationName</key>
                <string>Default Configuration</string>
                <key>UISceneStoryboardFile</key>
                <string>LaunchScreen</string>
            </dict>
        </array>
    </dict>
</dict>

Et une capture d'écran pour voir: enter image description here

Les principaux éléments à synchroniser sont:

  • Nom de classe délégué afin que Xcode sache où trouver votre fichier SceneDelegate
  • Nom de configuration pour que l'appel dans AppDelegate puisse charger le UISceneConfiguration correct

Après avoir fait cela, j'ai ensuite pu charger ma vue HomeList nouvellement créée (un objet SwiftUI)

6
CodeBender

Si vous cherchez à créer une vue SwiftUI à partir d'un ancien projet Objective C, cette technique a parfaitement fonctionné pour moi,

Voir Ajout de SwiftUI aux applications Objective-C

Félicitations à notre ami qui a écrit cela.

2
Snips
import Foundation
    #if canImport(SwiftUI)
    import SwiftUI

internal final class SomeRouter {
    fileprivate weak var presentingViewController: UIViewController!

    function navigateToSwiftUIView() {
       if #available(iOS 13, *) {
            let hostingController = UIHostingController(rootView: contentView())  
presentingViewController?.navigationController?.pushViewController(hostingController, animated: true)
            return
        }
        //Keep the old way when not 13.
}
#endif
1
patilh

Vous pouvez les utiliser ensemble. Vous pouvez "transférer" une UIView vers View par UIViewRepresentable conformité. Les détails peuvent être trouvés dans le tutoriel officiel .

Cependant, vous devez également considérer sa compatibilité.

Voici l'extrait de code du protocole View de SwiftUI :

///
/// You create custom views by declaring types that conform to the `View`
/// protocol. Implement the required `body` property to provide the content
/// and behavior for your custom view.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol View : _View {

    /// The type of view representing the body of this view.
    ///
    /// When you create a custom view, Swift infers this type from your
    /// implementation of the required `body` property.
    /// ...
}

Ce n'est donc pas rétrocompatible.

  • iOS 13.0+
  • macOS 10.15+
  • watchOS 6.0+
1
Mint