可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Is is possible to build views with SwiftUI side by side with an existing UIKit application?
I have an existing application written in Objective-C. I've begun migrating to Swift 5. I'm wondering if I can use SwiftUI alongside my existing UIKit .xib views.
That is to say I want some views built with SwiftUI and some other views built with UIKit in the same app. Not mixing the two of course.
SomeObjCSwiftProject/
SwiftUIViewController.swift
SwiftUIView.xib
UIKitViewController.swift
UIKitView.xib
Working alongside each other
回答1:
edit 05/06/19: Added information about UIHostingController as suggested by @Departamento B in his answer. Credits go to him!
Using SwiftUI with UIKit
One can use SwiftUI
components in existing UIKit
environments by wrapping a SwiftUI
View
into a UIHostingController
like this:
let swiftUIView = SomeSwiftUIView() // swiftUIView is View
let viewCtrl = UIHostingController(rootView: swiftUIView)
It's also possible to override UIHostingController
and customize it to one's needs, e. g. by setting the preferredStatusBarStyle
manually if it doesn't work via SwiftUI
as expected.
UIHostingController
is documented here.
Using UIKit with SwiftUI
If an existing UIKit
view should be used in a SwiftUI
environment, the UIViewRepresentable
protocol is there to help! It is documented here and can be seen in action in this official Apple tutorial.
Compatibility
Please note that UIKit
and SwiftUI
components can only be used in conjunction if the app targets iOS 13+, as SwiftUI
is only available iOS 13+. See this post for more information.
回答2:
UIHostingController
Although at the moment the documentation for the class has not been written, UIHostingController<Content>
seems to be what you're looking for: https://developer.apple.com/documentation/swiftui/uihostingcontroller
I've just tried it in my app with the following line of code:
let vc = UIHostingController(rootView: BenefitsSwiftUIView())
Where BenefitsSwiftUIView
is just the default "Hello World" View
from SwiftUI
. This works exactly as you expect it. It also works if you subclass UIHostingController
.
回答3:
If you want to embed SwiftUI into a UIKit view controller, use a Container View.
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)
}
}
Reference
回答4:
One item I have not seen mentioned yet, and involves Xcode 11 beta 5 (11M382q) involves updating your app's info.plist file.
For my scenario, I am taking an existing Swift & UIKit based application and fully migrating it to be an iOS 13 & pure SwiftUI app, so backwards compatibility is not a concern for me.
After making the necessary changes to AppDelegate:
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration",
sessionRole: connectingSceneSession.role)
}
And adding in a SceneDelegate class:
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()
}
}
}
I was encountering a problem where my SceneDelegate was not being called. This was fixed by adding the following into my info.plist file:
<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>
And a screenshot to see:
The main items to keep in sync are:
- Delegate Class Name so that Xcode knows where to find your
SceneDelegate
file
- Configuration Name so that the call in AppDelegate can load the correct
UISceneConfiguration
After doing this, I was then able to load my newly created HomeList view (A SwiftUI object)
回答5:
You can use them together. You can 'transfer' a UIView
to View
by UIViewRepresentable
conformance. Details can be found in the official tutorial.
However, you should also consider its compatibility.
Here is the code snippet from Protocol View
of 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.
/// ...
}
So it's not backwards compatible.
- iOS 13.0+
- macOS 10.15+
- watchOS 6.0+
回答6:
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
回答7:
If you're looking to create a SwiftIU view from a legacy Objective C project, then this technique worked perfectly for me,
See Adding SwiftUI to Objective-C Apps
Kudos to our friend who wrote that up.
回答8:
Others has been showcasing how to use UIHostingController.
I can show how you can present a UIViewController from SwiftUI UIViewControllerRepresentable:
struct YourViewControllerWrapper: UIViewControllerRepresentable {
typealias UIViewControllerType = YourViewController
func makeUIViewController(context: UIViewControllerRepresentableContext<YourViewControllerWrapper>) -> YourViewController {
let storyBoard = UIStoryboard(name: "YourStoryboard", bundle: Bundle.main)
return storyBoard.instantiateViewController(withIdentifier: "YourViewController") as! YourViewController
}
func updateUIViewController(_ uiViewController: YourViewController, context: UIViewControllerRepresentableContext<YourViewController>) {
// do nothing
}
}