Как обновить SwiftUI View в UIKit при изменении значения? - PullRequest
2 голосов
/ 06 февраля 2020

Я пытаюсь интегрировать представление SwiftUI, которое анимирует изменения в переменной @State (первоначально прогресс был @ Состояние частного прогресса: CGFloat = 0,5 в представлении SwiftUI), в существующее приложение UIKit. Я прочитал много результатов поиска по интеграции SwiftUI в UIKit (@State, @Binding, @Environment, et c.), Но не могу понять это.

Я создаю очень простой пример того, что я пытаюсь сделать, поскольку, как я полагаю, когда я увижу этот ответ, я могу применить его к своему существующему приложению.

Раскадровка - это просто просмотр контроллера с 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)
    }
}```

Ответы [ 2 ]

3 голосов
/ 06 февраля 2020

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

и все прекрасно работает вместе

enter image description here

2 голосов
/ 06 февраля 2020

Принятый ответ на самом деле не отвечает первоначальному вопросу «обновить SwiftUI View в UIKit ...»?

ИМХО, когда вы хотите взаимодействовать с 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)
  }
}
...