在 SwiftUI 中,如何在 UIView 中或作为 UIView 使用 UIHostingController?

In SwiftUI, how to use UIHostingController inside an UIView or as an UIView?

另一个问题标题可以是 "How to add an UIHostingController's view as subview for an UIView?".

我正在创建一个新的 UI 组件,很想尝试一下 SwiftUI。下图是当前的视图结构。 UIView 是我现在正在使用的视图(右上角),SwiftUI 视图是我尝试使用的视图(右下角)。

在我观看了 WWDC 2019 的所有 SwiftUI 视频之后。我仍然不知道如何使用 SwiftUI 视图并将其放在 UIView 的位置实例应该去。

我从 "Integrating SwiftUI" 谈话中注意到有一个针对 macOS 的 NSHostingViewhttps://developer.apple.com/documentation/swiftui/nshostingview# 这让我想知道是否有类似的东西或者有什么替代方法可以实现它。

我读到 之类的问题提到 SwiftUI 和 UIKit 可以与 UIHostingController 一起玩。然而,我想做的是只采用一小块 SwiftUI 并将它放在我现有的 UIKit 视图组件中,而不是将它用作控制器。

我是 iOS 开发的新手,如果有方法可以将视图控制器用作 UIView 视图,请发表评论。谢谢。

心里有数了。

  1. UIHostingController
  2. 包装 SwiftUI
  3. 初始化控制器
  4. 将新控制器添加为子视图控制器
  5. 将控制器视图作为子视图添加到它应该去的地方

因此:

addChild(hostingViewController)
hostingViewController.view.frame = ...
view.addSubview(hostingViewController.view)
hostingViewController.didMove(toParent: self)

A view controller always uses other view controllers as views.

Stanford CS193P, https://youtu.be/w7a79cx3UaY?t=679

参考

  • How to add an UIViewController's view as subview
  • Place UIViewController inside UIView

视图控制器不仅仅用于顶级场景。我们经常将视图控制器放在视图控制器中。它被称为“视图控制器包含”and/or“子视图控制器”。 (顺便说一句,视图控制器容器通常是对抗传统 UIKit 应用程序中视图控制器膨胀的好方法,它将复杂的场景分解为多个视图控制器。)

所以,

  • 继续使用UIHostingController:

    let controller = UIHostingController(rootView: ...)
    

    和;

  • 添加view controller之后可以添加hosting controller作为子view controller:

    addChild(controller)
    view.addSubview(controller.view)
    controller.didMove(toParent: self)
    

    显然,您还需要为托管控制器的 view 设置 frame 或布局约束。

    有关将一个视图控制器嵌入另一个视图控制器的一般信息,请参阅 UIViewController documentation 实现容器视图控制器 部分。


例如,假设我们有一个 SwiftUI 视图来渲染一个圆圈,其中包含文本:

struct CircleView : View {
    @ObservedObject var model: CircleModel

    var body: some View {
        ZStack {
            Circle()
                .fill(Color.blue)
            Text(model.text)
                .foregroundColor(Color.white)
        }
    }
}

假设这是我们的视图模型:

import Combine

class CircleModel: ObservableObject {
    @Published var text: String

    init(text: String) {
        self.text = text
    }
}

然后我们的 UIKit 视图控制器可以添加 SwiftUI 视图,在 UIView 中设置其 frame/constraints,并根据需要更新其模型:

import UIKit
import SwiftUI

class ViewController: UIViewController {
    private weak var timer: Timer?
    private var model = CircleModel(text: "")

    override func viewDidLoad() {
        super.viewDidLoad()

        addCircleView()
        startTimer()
    }

    deinit {
        timer?.invalidate()
    }
}

private extension ViewController {
    func addCircleView() {
        let circleView = CircleView(model: model)
        let controller = UIHostingController(rootView: circleView)
        addChild(controller)
        controller.view.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(controller.view)
        controller.didMove(toParent: self)

        NSLayoutConstraint.activate([
            controller.view.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),
            controller.view.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5),
            controller.view.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            controller.view.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }

    func startTimer() {
        var index = 0
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
            index += 1
            self?.model.text = "Tick \(index)"
        }
    }
}