值更改时,如何更新 UIKit 中的 SwiftUI 视图?

Dan*_*her 5 variables state uikit swiftui

我正在尝试将一个 SwiftUI 视图集成到现有的 UIKit 应用程序中,该视图对@State 变量的更改进行动画处理(最初的进度是@State 私有进度:CGFloat = 0.5在 SwiftUI 视图中)。我已经阅读了大量关于将 SwiftUI 集成到 UIKit(@State、@Binding、@Environment 等)的搜索结果,但似乎无法弄清楚这一点。

我创建了一个非常简单的示例来说明我正在尝试做什么,因为我相信一旦我看到这个答案,我就可以将它应用到我现有的应用程序中。

Storyboard 只是带有 UISlider 的视图控制器。下面的代码显示了 SwiftUI 视图,但我希望它在我移动 UISlider 时更新。

import SwiftUI

class ViewController: UIViewController {

    var progress: CGFloat = 0.5

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        let frame = CGRect(x: 20, y: 200, width: 400, height: 400)

        let childView = UIHostingController(rootView: Animate_Trim(progress: progress))
        addChild(childView)
        childView.view.frame = frame
        view.addSubview(childView.view)
        childView.didMove(toParent: self)
    }

    @IBAction func sliderAction(_ sender: UISlider) {
        progress = CGFloat(sender.value)
        print("Progress: \(progress)")
    }

}

struct Animate_Trim: View {
    var progress: CGFloat

    var body: some View {
        VStack(spacing: 20) {

            Circle()
                .trim(from: 0, to: progress) // Animate trim
                .stroke(Color.blue,
                        style: StrokeStyle(lineWidth: 40,
                                           lineCap: CGLineCap.round))
                .frame(height: 300)
                .rotationEffect(.degrees(-90)) // Start from top
                .padding(40)
                .animation(.default)

            Spacer()

        }.font(.title)
    }
}```
Run Code Online (Sandbox Code Playgroud)

MrA*_*An3 10

如果您不想使用通知中心。您可以仅使用 @Published 并分配或接收。

我在 Playground 中编写了一个工作示例来展示这个概念:

//This code runs on Xcode playground
import Combine
import SwiftUI

class ObservableSlider: ObservableObject {
    @Published public var value: Double = 0.0
}

class YourViewController {
    var observableSlider:ObservableSlider = ObservableSlider()
    private var cancellables: Set<AnyCancellable> = []
    let hostController = YourHostingController() // I put it here for the sake of the example, but you do need a reference to the Hosting Controller.

    init(){ // In a real VC this code would probably be on viewDidLoad

        let swiftUIView = hostController.rootView

        //This is where values of SwiftUI view and UIKit get glued together
        self.observableSlider.$value.assign(to: \.observableSlider.value, on: swiftUIView).store(in:&self.cancellables)
    }
    func updateSlider() {
        observableSlider.value = 8.5
    }
}

// In real app it would be something like:
//class YourHostingController<YourSwiftUIView> UIHostingController
class YourHostingController {
    var rootView = YourSwiftUIView()

//In a real Hosting controller you would do something like:
//    required init?(coder aDecoder: NSCoder){
//         super.init(coder: aDecoder, rootView: YourSwiftUIView())
//     }
}

struct YourSwiftUIView: View{
    var body: some View {
        EmptyView() // Your real SwiftUI body...
    }
    @ObservedObject var observableSlider: ObservableSlider = ObservableSlider()
    func showValue(){
        print(observableSlider.value)
    }
    init(){
        print(observableSlider.value)
    }
}

let yourVC = YourViewController() // Inits view and prints 0.0
yourVC.updateSlider() // Updates from UIKit to 8.5
yourVC.hostController.rootView.showValue() // Value in SwiftUI View is updated (prints 8.5)
Run Code Online (Sandbox Code Playgroud)


nin*_*nes 5

接受的答案实际上没有回答原始问题“在UIKit 中更新 SwiftUI 视图...”?

恕我直言,当您想与 UIKit 交互时,您可以使用通知来更新进度视图:

extension Notification.Name {
  static var progress: Notification.Name { return .init("progress") }
}
class ViewController: UIViewController {
  var progress: CGFloat = 0.5 {
    didSet {
      let userinfo: [String: CGFloat] = ["progress": self.progress]
      NotificationCenter.default.post(Notification(name: .progress,
                                                   object: nil,
                                                   userInfo: userinfo))
    }
  }
  var slider: UISlider = UISlider()
  override func viewDidLoad() {
    super.viewDidLoad()
    slider.addTarget(self, action: #selector(sliderAction(_:)), for: .valueChanged)
    slider.frame = CGRect(x: 0, y: 500, width: 200, height: 50)
    // Do any additional setup after loading the view.

    let frame = CGRect(x: 20, y: 200, width: 400, height: 400)

    let childView = UIHostingController(rootView: Animate_Trim())
    addChild(childView)
    childView.view.frame = frame
    view.addSubview(childView.view)
    view.addSubview(slider)
    childView.didMove(toParent: self)
  }

  @IBAction func sliderAction(_ sender: UISlider) {
    progress = CGFloat(sender.value)
    print("Progress: \(progress)")
  }
}

struct Animate_Trim: View {
  @State var progress: CGFloat = 0
  var notificationChanged = NotificationCenter.default.publisher(for: .progress)
  var body: some View {
    VStack(spacing: 20) {
      Circle()
        .trim(from: 0, to: progress) // Animate trim
        .stroke(Color.blue,
                style: StrokeStyle(lineWidth: 40,
                                   lineCap: CGLineCap.round))
        .frame(height: 300)
        .rotationEffect(.degrees(-90)) // Start from top
        .padding(40)
        .animation(.default)
        .onReceive(notificationChanged) { note in
          self.progress = note.userInfo!["progress"]! as! CGFloat
      }
      Spacer()
    }.font(.title)
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 这看起来有点矫枉过正,有没有更简单的方法可以从 UIKit 更新 SwiftUI 中的 @Binding 变量? (3认同)