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

Tho*_*eia 2 declarative swift swiftui

所以也许我误解了 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()
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

这样做的正确方法是什么?

谢谢

Pro*_*in8 9

是的,你想的有点不对。@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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

现在在你看来:

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)
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

但更好的是将您的聆听包装在Combine.Publisher发布这些事件的自定义中。我将把它作为一个单独的问题,如何做到这一点。