So maybe I'm misunderstanding how SwiftUI works, but I've been trying to do this for over an hour and still can't figure it out.
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()
}
}
So the idea is really simple. I have an external midi keyboard using AudioKit. When a key on the keyboard is pressed, the rectangle should change from white to red.
The receivedMIDINoteOn
function is being called and 'foo' is printed to the console, and despite keyOn.toggle()
appearing in the same function, this still won't work.
What's the proper way to do this?
Thanks
Yes, you are thinking of it slightly wrong. @State
is typically for internal state changes. Have a button that your View
directly references? Use @State
. @Binding
should be used when you don't (or shouldn't, at least) own the state. Typically, I use this when I have a parent view who should be influencing or be influenced by a subview.
But what you are likely looking for, is @ObservedObject
. This allows an external object to publish changes and your View
subscribes to those changes. So if you have some midi listening object, make it an 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
}
}
}
Now in your view:
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)
}
}
}
}
But what would be even better is to wrap your listening in a custom Combine.Publisher
that posts these events. I will leave that as a separate question, how to do that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With