如何更新嵌入到 UIKit 中的 SwiftUI 视图?

Sen*_*ful 5 uikit ios swift swiftui

我正在为 UIKit寻找 AppKit 的NSHostingView的等效,以便我可以在 UIKit 中嵌入 SwiftUI 视图。不幸的是,UIKit 没有与NSHostingView. 我们所拥有的最接近NSHostingController的等效,名为UIHostingController。由于视图控制器包含视图,我们应该能够调用适当的 UIViewController 嵌入方法,然后view直接抓取并使用它。

目前 许多 文章解释,这是方式嵌入里面的UIKit一个SwiftUI视图。但是,他们通常无法解释您将如何通过UIKit 进行交流斯威夫特用户界面。例如,假设我实现了一个用作进度条的 SwiftUI 视图,我希望定期更新进度。我希望我的旧版/UIKit 代码更新 SwiftUI 视图以显示新进度。

我发现唯一一篇接近于解释如何操作嵌入视图的内容的文章建议我们这样做@ObservedObject

import UIKit
import SwiftUI
import Combine

class CircleModel: ObservableObject {
    var didChange = PassthroughSubject<Void, Never>()

    var text: String { didSet { didChange.send() } }

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

struct CircleView : View {
    @ObservedObject var model: CircleModel

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

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)"
        }
    }
}

Run Code Online (Sandbox Code Playgroud)

这似乎是有道理的,因为计时器应该触发更新视图的一系列事件:

  1. ? self?.model.text = "Tick 1"(在ViewController.startTimer())。
  2. ? didChange.send()(在CircleModel.text.didSet)
  3. ? Text(model.text)(在CircleView.body)

正如您通过指标(指定是否运行某事)所见,问题在于didChange.send()永远不会触发CircleView.body.

如何从 UIKit > SwiftUI 进行通信以操作嵌入在 UIKit 中的 SwiftUI 视图?

Asp*_*eri 7

您所需要的只是扔掉那个自定义主题,并使用标准@Published,如下所示

class CircleModel: ObservableObject {

    @Published var text: String

    init(text: String) {
        self.text = text
    }
}
Run Code Online (Sandbox Code Playgroud)

测试:Xcode 11.2 / iOS 13.2

  • 对于其他任何想知道如何获取 model.text 作为 SwiftUI 视图内读/写访问的绑定,但将其与 SwiftUI 外部的变量同步的人,而不是 model.text,它只是 $model.text 因为 \ @ObservableObject 的包装值是一个绑定,可以像绑定到包装的 \@State 属性一样使用。基于绑定的 SwiftUI 视图代码无需更改。(CircleModel 将使用包装器 \@ObservedObject 而不是 \@State 或只读普通属性来声明。)(忽略 \ 字符。) (2认同)