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
edit 05/06/19: Ajout d'informations sur UIHostingController comme suggéré par @Departamento B dans sa réponse. Les crédits vont à lui !
On peut utiliser des composants SwiftUI
dans des environnements UIKit
existants en enveloppant un SwiftUI
View
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 .
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.
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())
Où 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
.
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)
}
}
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:
Les principaux éléments à synchroniser sont:
SceneDelegate
UISceneConfiguration
correctAprès avoir fait cela, j'ai ensuite pu charger ma vue HomeList nouvellement créée (un objet SwiftUI)
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.
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
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.