【问题标题】:SwiftUI: Change @State variable through a function called externally?SwiftUI:通过外部调用的函数更改@State 变量?
【发布时间】:2019-12-17 07:09:25
【问题描述】:

所以也许我误解了 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() 出现在同一个函数中,但这仍然不起作用。

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

谢谢

【问题讨论】:

  • 这是一个如何调用receivedMIDINoteOn 的问题。 ContentView 是一个值,所以如果你传递某个地方 ContentView() 你只是传递一个视图的副本,它不同于淹没在视图层次结构中的那个。相反,您必须使用 ObservableObject 视图模型作为侦听器。
  • 好的,现在我的 ContentView 中有这个可观察对象。当我尝试使用midi.addListener(contentView.keyboardObject) 抓取它以将其添加为侦听器时,我收到错误表达式类型“()”不明确。这里发生了什么?如何使用这个 observable 对象?
  • 好的,所以我只是在该表达式的末尾添加了“as AKMIDIListener”,它就起作用了。但是,现在我收到错误“[SwiftUI] 不允许从后台线程发布更改;”它不起作用。好吧,AudioKit 使用后台线程不是我的错——我该如何解决这个问题?
  • 在回调中使用DispatchQueue.main.async {} 重定向到主队列。

标签: swift swiftui declarative


【解决方案1】:

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

【讨论】:

    猜你喜欢
    • 2020-04-10
    • 1970-01-01
    • 2022-11-14
    • 1970-01-01
    • 1970-01-01
    • 2019-11-10
    • 2023-03-18
    • 2020-04-10
    • 1970-01-01
    相关资源
    最近更新 更多