在现有的UIKit应用程序中包含SwiftUI视图

vis*_*isc 32 swift swiftui

是否可以通过SwiftUI与现有UIKit应用程序并排构建视图?

我有一个用Objective-C编写的现有应用程序。我已经开始迁移到Swift5。我想知道是否可以将SwiftUI与现有的UIKit .xib视图一起使用。

也就是说,我希望在同一应用程序中使用SwiftUI构建一些视图,并使用UIKit构建一些其他视图。当然不要混合两者。

SomeObjCSwiftProject/
    SwiftUIViewController.swift
    SwiftUIView.xib
    UIKitViewController.swift
    UIKitView.xib
Run Code Online (Sandbox Code Playgroud)

彼此一起工作

fre*_*dpi 36

编辑05/06/19:添加了有关UIHostingController的信息,如@Departamento B在其答案中所建议。学分归他所有!


将SwiftUI与UIKit结合使用

可以通过将a包裹成这样,SwiftUI在现有UIKit环境中使用组件:SwiftUI ViewUIHostingController

let swiftUIView = SomeSwiftUIView() // swiftUIView is View
let viewCtrl = UIHostingController(rootView: swiftUIView)
Run Code Online (Sandbox Code Playgroud)

也可以根据需要改写UIHostingController和自定义它,例如,preferredStatusBarStyle如果无法正常使用,则通过手动设置SwiftUI

UIHostingController记录在这里


将UIKit与SwiftUI结合使用

如果UIKit应该在SwiftUI环境中使用现有视图,则可以使用该UIViewRepresentable协议!它已在此处记录,可以在 Apple官方教程中实际使用。


兼容性

请注意,仅当该应用程序面向iOS 13+时,才可以UIKit与和SwiftUI组件一起使用,这SwiftUI仅适用于iOS 13+。有关更多信息,请参见此帖子。

  • `UIViewRepresentable` 似乎相反,允许将 `UIView` 添加到 `SwiftUI` 层次结构中 (7认同)
  • 使用 SwiftUI 时可以面向 iOS 12 及更低版本。当然,SwiftUI 仅适用于 iOS 13,对于较低的 iOS 版本,您需要制作 UIKit 视图(重复工作...)。但对于那些正在移民的人来说,可能会有所帮助。更多信息在这里:/sf/answers/4086081821/ (2认同)

Mik*_*Lee 27

如果要将 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)
    }
}
Run Code Online (Sandbox Code Playgroud)

参考

  • 这是这里最完整的答案。此外,在 iOS 15 上使用模拟器时,它比其他正确放置 SwiftUI 视图的方法更有效。 (5认同)

Jas*_*ues 10

我是这样做的:

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

}
Run Code Online (Sandbox Code Playgroud)

添加到视图控制器如下:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这是包装视图的代码

对于这个非常简单的情况,包装视图使用手动布局(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
    }
}
Run Code Online (Sandbox Code Playgroud)


Dep*_*o B 9

UIHostingController

尽管此刻该类的文档尚未编写,但UIHostingController<Content>似乎正是您所需要的:https : //developer.apple.com/documentation/swiftui/uihostingcontroller

我刚刚在我的应用中使用以下代码行对其进行了尝试:

let vc = UIHostingController(rootView: BenefitsSwiftUIView())
Run Code Online (Sandbox Code Playgroud)

其中BenefitsSwiftUIView仅仅是默认的“Hello World” ViewSwiftUI。这完全符合您的预期。如果您继承了子类,它也可以工作UIHostingController

  • 您不需要显式指定泛型参数,可以推断出它;) (2认同)

Cod*_*der 8

我尚未提及的一项涉及 Xcode 11 beta 5 (11M382q) 涉及更新应用程序的 info.plist 文件。

对于我的场景,我正在使用现有的基于 Swift 和 UIKit 的应用程序并将其完全迁移为 iOS 13 和纯 SwiftUI 应用程序,因此向后兼容性对我来说不是问题。

对 AppDelegate 进行必要的更改后:

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

并添加一个 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()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我遇到了一个问题,我的 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>
Run Code Online (Sandbox Code Playgroud)

和一个截图: 在此处输入图片说明

保持同步的主要项目是:

  • 委托类名,以便 Xcode 知道在哪里可以找到您的SceneDelegate文件
  • 配置名称,以便 AppDelegate 中的调用可以加载正确的UISceneConfiguration

这样做之后,我就可以加载我新创建的 HomeList 视图(一个 SwiftUI 对象)


Mos*_*sam 8

如果您遇到布局问题,您必须向 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)
    }
}
Run Code Online (Sandbox Code Playgroud)

使用这个扩展:

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
    }
}
Run Code Online (Sandbox Code Playgroud)