【问题标题】:How do I update a SwiftUI View in UIKit when value changes?值更改时如何在 UIKit 中更新 SwiftUI 视图?
【发布时间】:2020-02-06 00:19:30
【问题描述】:

我正在尝试将一个 SwiftUI 视图集成到一个现有的 UIKit 应用程序中,该视图在更改 @State 变量时进行动画处理(最初的进度是 @State 私有进度:SwiftUI 视图中的 CGFloat = 0.5) .我已经阅读了大量关于将 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)
    }
}```

【问题讨论】:

    标签: variables uikit state swiftui


    【解决方案1】:

    接受的答案实际上并没有回答原始问题“在 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)
      }
    }
    

    【讨论】:

    • 这看起来有点矫枉过正,没有更简单的方法可以从 UIKit 更新 SwiftUI 中的 @Binding 变量吗?
    【解决方案2】:

    如果您不想使用通知中心。您可以只使用 @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)
    

    【讨论】:

      【解决方案3】:

      Combine 是你的朋友......

      1. 创建模型
      2. 从 UISlider 或应用程序的任何其他部分更新模型
      3. 使用模型更新您的 SwiftUI.View

      我做了简单的例子,只使用 SwiftUI,但使用相同的场景

      import SwiftUI
      
      class Model: ObservableObject {
          @Published var progress: Double = 0.2
      
      }
      
      struct SliderView: View {
          @EnvironmentObject var slidermodel: Model
          var body: some View {
      
              // this is not part of state of the View !!!
              // the bindig is created directly to your global EnnvironmentObject
      
              // be sure that it is available by
              // creating the SwiftUI view that provides the window contents
              // in your SceneDelegate.scene(....)
              // let model = Model()
              // let contentView = ContentView().environmentObject(model)
      
              let binding = Binding<Double>(get: { () -> Double in
                  self.slidermodel.progress
              }) { (value) in
                  self.slidermodel.progress = value
              }
      
              return Slider(value: binding, in: 0.0 ... 1.0)
          }
      }
      
      
      struct ContentView: View {
          @EnvironmentObject var model: Model
          var body: some View {
              VStack {
      
                  Circle()
                      .trim(from: 0, to: CGFloat(model.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)
      
                  SliderView()
              }
          }
      }
      
      struct ContentView_Previews: PreviewProvider {
          static var previews: some View {
              ContentView().environmentObject(Model())
          }
      }
      

      一切都很好地协同工作

      【讨论】:

      • 这很酷,但没有直接回答关于从 UIKit 更新 SwiftUI 的 OP
      • 如前所述,这根本不使用 UIKit。
      猜你喜欢
      • 2020-03-31
      • 1970-01-01
      • 2020-06-12
      • 2020-10-22
      • 2020-05-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-30
      相关资源
      最近更新 更多