在现有 UIKit 应用程序中包含 SwiftUI 视图
Include SwiftUI views in existing UIKit application
是否可以使用 SwiftUI 与现有 UIKit 应用程序并排构建视图?
我有一个用 Objective-C 编写的现有应用程序。我已经开始迁移到 Swift 5。我想知道我是否可以将 SwiftUI 与我现有的 UIKit .xib 视图一起使用。
也就是说我想要在同一个应用程序中使用 SwiftUI 构建的一些视图和使用 UIKit 构建的其他一些视图。当然不能把两者混在一起。
SomeObjCSwiftProject/
SwiftUIViewController.swift
SwiftUIView.xib
UIKitViewController.swift
UIKitView.xib
并肩工作
编辑 05/06/19:添加了有关 UIHostingController 的信息,如@Departamento B 在他的回答中所建议的那样。积分归于他!
在 UIKit 中使用 SwiftUI
通过将 SwiftUI
View
包装到 UIHostingController
中,可以在现有 UIKit
环境中使用 SwiftUI
组件:
let swiftUIView = SomeSwiftUIView() // swiftUIView is View
let viewCtrl = UIHostingController(rootView: swiftUIView)
也可以覆盖 UIHostingController
并根据需要对其进行自定义,例如。 G。如果无法通过 SwiftUI
按预期工作,请手动设置 preferredStatusBarStyle
。
UIHostingController
已记录 here。
在 SwiftUI 中使用 UIKit
如果要在 SwiftUI
环境中使用现有的 UIKit
视图,UIViewRepresentable
协议可以提供帮助!已记录 here and can be seen in action in this Apple 官方教程。
兼容性
请注意,您不能在 iOS 版本 < iOS 13 上使用 SwiftUI
,因为 SwiftUI
仅适用于 iOS 13 及更高版本。有关详细信息,请参阅 post。
如果您想在目标低于 iOS 13 的项目中使用 SwiftUI
,您需要使用 @available(iOS 13.0.0, *)
属性标记您的 SwiftUI
结构。
您可以将它们一起使用。您可以通过 UIViewRepresentable
一致性 'transfer' UIView
到 View
。详情请见official tutorial.
但是,您还应该考虑它的兼容性。
这是 SwiftUI 协议 View
中的代码片段:
///
/// 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.
/// ...
}
所以它不向后兼容。
- iOS 13.0+
- macOS 10.15+
- watchOS 6.0+
UIHostingController
虽然目前 class 的文档尚未编写,UIHostingController<Content>
似乎是您要查找的内容:https://developer.apple.com/documentation/swiftui/uihostingcontroller
我刚刚在我的应用程序中使用以下代码行进行了尝试:
let vc = UIHostingController(rootView: BenefitsSwiftUIView())
其中 BenefitsSwiftUIView
只是 SwiftUI
中的默认值 "Hello World" View
。这完全符合您的预期。如果你 subclass UIHostingController
.
它也有效
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
我还没有看到提到的一个项目,涉及 Xcode 11 beta 5 (11M382q) 涉及更新应用程序的 info.plist 文件。
对于我的场景,我正在采用现有的 Swift & UI 基于套件的应用程序并将其完全迁移为 iOS 13 & pure SwiftUI 应用程序,所以向后兼容性对我来说不是问题。
对 AppDelegate 进行必要的更改后:
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration",
sessionRole: connectingSceneSession.role)
}
并添加一个 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()
}
}
}
我遇到了一个问题,我的 SceneDelegate 没有被调用。通过将以下内容添加到我的 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>
还有截图看看:
要保持同步的主要项目是:
- 委托 Class 姓名 以便 Xcode 知道在哪里可以找到您的
SceneDelegate
文件
- Configuration Name以便AppDelegate中的调用能够加载正确的
UISceneConfiguration
这样做之后,我就可以加载我新创建的 HomeList 视图(一个 SwiftUI 对象)
如果您想将 SwiftUI 嵌入到 UIKit 视图控制器中,请使用容器视图。
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)
}
}
如果您希望从遗留 Objective C 项目创建 SwiftUI
视图,那么这种技术对我来说非常有用,
见Adding SwiftUI to Objective-C Apps
感谢写下这篇文章的朋友。
有了故事板
您可以在界面生成器中使用 HotingViewController
组件:
那么如果你有一个像这样的简单 HotingController
:
class MySwiftUIHostingController: UIHostingController<Text> {
required init?(coder: NSCoder) {
super.init(coder: coder, rootView: Text("Hello World"))
}
}
你可以设置为控制器的自定义class:
有代码
let mySwiftUIHostingController = UIHostingController(rootView: Text("Hello World"))
然后就可以正常使用了UIViewController
重要提示
不要忘记在需要的地方导入 SwiftUI
UIHostingController
如果您遇到布局问题,您必须为 UIHostingController 的视图添加约束,
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.addConstrained(subview: childView.view)
childView.didMove(toParent: self)
}
}
使用此扩展程序:
extension UIView {
func addConstrained(subview: UIView) {
addSubview(subview)
subview.translatesAutoresizingMaskIntoConstraints = false
subview.topAnchor.constraint(equalTo: topAnchor).isActive = true
subview.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
subview.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
subview.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
}
这是我的做法:
创建 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)
}
}
添加到视图控制器如下:
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;
}
}
这是环绕视图的代码
对于这种非常简单的情况,环绕视图使用手动布局 (layoutSubViews) 而不是自动布局。
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
}
}
您只需按照以下步骤即可实现...
在 mainStoryBoard 上显示的 viewController 中创建一个按钮并导入 SwiftUI
在 mainStoryboard 中添加一个 hostingViewController 并将一个 Segue(显示 Segue)从按钮拖到 hostingViewController。Drag segue from storyBoard to SwiftUI
通过单击 Cmd+N 在您的项目中添加 SwiftUI 文件
Creating SwiftUI file
将在 mainStoryBoard 中呈现的 segueAction 表单添加到 viewController。
在segueAction中写入以下代码行...
@IBSegueAction func ActionMe(_ coder: NSCoder) -> UIViewController? {
return UIHostingController(coder: coder, rootView: SWIFTUI())
}
是否可以使用 SwiftUI 与现有 UIKit 应用程序并排构建视图?
我有一个用 Objective-C 编写的现有应用程序。我已经开始迁移到 Swift 5。我想知道我是否可以将 SwiftUI 与我现有的 UIKit .xib 视图一起使用。
也就是说我想要在同一个应用程序中使用 SwiftUI 构建的一些视图和使用 UIKit 构建的其他一些视图。当然不能把两者混在一起。
SomeObjCSwiftProject/
SwiftUIViewController.swift
SwiftUIView.xib
UIKitViewController.swift
UIKitView.xib
并肩工作
编辑 05/06/19:添加了有关 UIHostingController 的信息,如@Departamento B 在他的回答中所建议的那样。积分归于他!
在 UIKit 中使用 SwiftUI
通过将 SwiftUI
View
包装到 UIHostingController
中,可以在现有 UIKit
环境中使用 SwiftUI
组件:
let swiftUIView = SomeSwiftUIView() // swiftUIView is View
let viewCtrl = UIHostingController(rootView: swiftUIView)
也可以覆盖 UIHostingController
并根据需要对其进行自定义,例如。 G。如果无法通过 SwiftUI
按预期工作,请手动设置 preferredStatusBarStyle
。
UIHostingController
已记录 here。
在 SwiftUI 中使用 UIKit
如果要在 SwiftUI
环境中使用现有的 UIKit
视图,UIViewRepresentable
协议可以提供帮助!已记录 here and can be seen in action in this Apple 官方教程。
兼容性
请注意,您不能在 iOS 版本 < iOS 13 上使用 SwiftUI
,因为 SwiftUI
仅适用于 iOS 13 及更高版本。有关详细信息,请参阅
如果您想在目标低于 iOS 13 的项目中使用 SwiftUI
,您需要使用 @available(iOS 13.0.0, *)
属性标记您的 SwiftUI
结构。
您可以将它们一起使用。您可以通过 UIViewRepresentable
一致性 'transfer' UIView
到 View
。详情请见official tutorial.
但是,您还应该考虑它的兼容性。
这是 SwiftUI 协议 View
中的代码片段:
///
/// 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.
/// ...
}
所以它不向后兼容。
- iOS 13.0+
- macOS 10.15+
- watchOS 6.0+
UIHostingController
虽然目前 class 的文档尚未编写,UIHostingController<Content>
似乎是您要查找的内容:https://developer.apple.com/documentation/swiftui/uihostingcontroller
我刚刚在我的应用程序中使用以下代码行进行了尝试:
let vc = UIHostingController(rootView: BenefitsSwiftUIView())
其中 BenefitsSwiftUIView
只是 SwiftUI
中的默认值 "Hello World" View
。这完全符合您的预期。如果你 subclass UIHostingController
.
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
我还没有看到提到的一个项目,涉及 Xcode 11 beta 5 (11M382q) 涉及更新应用程序的 info.plist 文件。
对于我的场景,我正在采用现有的 Swift & UI 基于套件的应用程序并将其完全迁移为 iOS 13 & pure SwiftUI 应用程序,所以向后兼容性对我来说不是问题。
对 AppDelegate 进行必要的更改后:
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration",
sessionRole: connectingSceneSession.role)
}
并添加一个 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()
}
}
}
我遇到了一个问题,我的 SceneDelegate 没有被调用。通过将以下内容添加到我的 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>
还有截图看看:
要保持同步的主要项目是:
- 委托 Class 姓名 以便 Xcode 知道在哪里可以找到您的
SceneDelegate
文件 - Configuration Name以便AppDelegate中的调用能够加载正确的
UISceneConfiguration
这样做之后,我就可以加载我新创建的 HomeList 视图(一个 SwiftUI 对象)
如果您想将 SwiftUI 嵌入到 UIKit 视图控制器中,请使用容器视图。
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)
}
}
如果您希望从遗留 Objective C 项目创建 SwiftUI
视图,那么这种技术对我来说非常有用,
见Adding SwiftUI to Objective-C Apps
感谢写下这篇文章的朋友。
有了故事板
您可以在界面生成器中使用 HotingViewController
组件:
那么如果你有一个像这样的简单 HotingController
:
class MySwiftUIHostingController: UIHostingController<Text> {
required init?(coder: NSCoder) {
super.init(coder: coder, rootView: Text("Hello World"))
}
}
你可以设置为控制器的自定义class:
有代码
let mySwiftUIHostingController = UIHostingController(rootView: Text("Hello World"))
然后就可以正常使用了UIViewController
重要提示
不要忘记在需要的地方导入 SwiftUI
UIHostingController
如果您遇到布局问题,您必须为 UIHostingController 的视图添加约束,
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.addConstrained(subview: childView.view)
childView.didMove(toParent: self)
}
}
使用此扩展程序:
extension UIView {
func addConstrained(subview: UIView) {
addSubview(subview)
subview.translatesAutoresizingMaskIntoConstraints = false
subview.topAnchor.constraint(equalTo: topAnchor).isActive = true
subview.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
subview.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
subview.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
}
这是我的做法:
创建 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)
}
}
添加到视图控制器如下:
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;
}
}
这是环绕视图的代码
对于这种非常简单的情况,环绕视图使用手动布局 (layoutSubViews) 而不是自动布局。
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
}
}
您只需按照以下步骤即可实现...
在 mainStoryBoard 上显示的 viewController 中创建一个按钮并导入 SwiftUI
在 mainStoryboard 中添加一个 hostingViewController 并将一个 Segue(显示 Segue)从按钮拖到 hostingViewController。Drag segue from storyBoard to SwiftUI
通过单击 Cmd+N 在您的项目中添加 SwiftUI 文件 Creating SwiftUI file
将在 mainStoryBoard 中呈现的 segueAction 表单添加到 viewController。
在segueAction中写入以下代码行...
@IBSegueAction func ActionMe(_ coder: NSCoder) -> UIViewController? {
return UIHostingController(coder: coder, rootView: SWIFTUI())
}