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

SwiftUI: Change @State variable through a function called externally?

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

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

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

正在调用 receivedMIDINoteOn 函数并且 'foo' 被打印到控制台,尽管 keyOn.toggle() 出现在同一个函数中,这仍然不起作用。

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

谢谢

是的,你想的有点不对。 @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 中。我会把它作为一个单独的问题,如何做。