SwiftUI:通过外部调用的函数更改@State变量?

时间:2019-12-17 07:09:25

标签: swift swiftui declarative

所以也许我误解了SwiftUI的工作原理,但是我已经尝试了一个多小时,但仍然无法弄清。

LogProperties

所以这个想法真的很简单。我有一个使用AudioKit的外部Midi键盘。当按下键盘上的某个键时,矩形应从白色变为红色。

正在调用struct ContentView: View, AKMIDIListener { @State var keyOn: Bool = false var key: Rectangle = Rectangle() var body: some View { VStack() { Text("Foo") key .fill(keyOn ? Color.red : Color.white) .frame(width: 30, height: 60) } .frame(width: 400, height: 400, alignment: .center) } func receivedMIDINoteOn(noteNumber: MIDINoteNumber, velocity: MIDIVelocity, channel: MIDIChannel, portID: MIDIUniqueID? = nil, offset: MIDITimeStamp = 0) { print("foo") keyOn.toggle() } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } 函数,并且将'foo'打印到控制台,尽管receivedMIDINoteOn出现在同一函数中,但这仍然行不通。

执行此操作的正确方法是什么?

谢谢

1 个答案:

答案 0 :(得分:2)

是的,您认为它有些错误。 @State通常用于内部状态更改。是否有您的View直接引用的按钮?使用@State。如果您不(或至少不应)拥有该州,则应使用@Binding。通常,当我有一个应该影响子视图或受子视图影响的父视图时,才使用此方法。

但是您可能想要的是@ObservedObject。这允许外部对象发布更改,而您的View订阅这些更改。因此,如果您有一些Midi监听对象,请将其设为ObservableObject

final class MidiListener: ObservableObject, AKMIDIListener {
  // 66 key keyboard, for example
  @Published var pressedKeys: [Bool] = Array(repeating: false, count: 66)

  init() {
    // set up whatever private storage/delegation you need here
  }

  func receivedMIDINoteOn(noteNumber: MIDINoteNumber, velocity: MIDIVelocity, channel: MIDIChannel, portID: MIDIUniqueID? = nil, offset: MIDITimeStamp = 0) {
    // how you determine which key(s) should be pressed is up to you. if this is monophonic the following will suffice while if it's poly, you'll need to do more work
    DispatchQueue.main.async {
      self.pressedKeys[Int(noteNumber)] = true
    }
  }
}

现在在您看来:

struct KeyboardView: View {
  @ObservedObject private var viewModel = MidiListener()

  var body: some View {
    HStack {
      ForEach(0..<viewModel.pressedKeys.count) { index in
        Rectangle().fill(viewModel.pressedKeys[index] ? Color.red : Color.white)
      }
    }
  }
}

但是更好的方法是将您的收听内容封装在发布这些事件的自定义Combine.Publisher中。我将把它作为一个单独的问题,如何做。