GarageBand aborts when clicking on any SwiftUI button in AudioUnit plugin

I'm developing an AudioUnit plugin using SwiftUI for the front end. When I run my plugin in GarageBand on macOS, clicking on any SwiftUI buttons in the interface causes GarageBand to abort -- I see this message near the top of the Problem Report:

abort() called Unrecognized selector -[_TtC7SwiftUIP33_9FEBA96B0BC70E1682E82D239F242E7311Coordinator lastClickedParameterView]

It appears that something in GarageBand expects a method named lastClickedParameterView to be implemented somewhere? Am I supposed to implement this method? If so, where do I implement it, and where can I find documentation for it?

The plugin has a small host application as part of the project, and when I run that host application in Xcode or from DerivedData, buttons work normally and as I expect. AudioUnits, strictly speaking, don't seem to require this method -- it seems to be something that only GarageBand and Logic require?

I ran into this problem in GarageBand 10.3.5 on macOS 10.15.7 with an app built in Xcode 12.4 to target macOS 10.15. I upgraded to macOS 11.4, Xcode 12.5, and GarageBand 10.4.3, and built the app targeting macos 11.3, and see the same problem.

  • This actually works in GarageBand, by the way -- I defined my own View, IsleButton, to look and act exactly like a Button, and replaced my usages of Button with IsleButton:

    import SwiftUI struct IsleButton: View { var label: String var action: () -> Void @State var backgroundColor = Color(red: 0.4, green: 0.4, blue: 0.4) var body: some View { Group { Text(label) } .padding(EdgeInsets(top: 2.0, leading: 10.0, bottom: 2.5, trailing: 10.0)) .background(RoundedRectangle(cornerRadius: 5.0).fill(backgroundColor)) .gesture(DragGesture(minimumDistance: 0) .onChanged({ _ in backgroundColor = Color.blue action() }) .onEnded({ _ in backgroundColor = Color(red: 0.4, green: 0.4, blue: 0.4) }) ) } } import SwiftUI struct SystemLevelView: View { @ObservedObject var patchBank: PatchBank @State var dialogDisplayed: Bool = false var body: some View { GeometryReader { parent in ZStack { VStack { HStack { /Button("System-Wide Oscillators") { dialogDisplayed = true }/ IsleButton(label: "System-Wide Oscillators", action: { dialogDisplayed = true }) Spacer() /*Button("System-Wide Effects Send") {}*/ IsleButton(label: "System-Wide Effects Send", action: {}) } .padding(6.0) Spacer() } HStack { Spacer() VStack(alignment: .center) { ParameterSliderView(parameter: patchBank.gain, minMaxLabelToggle: .constant(false)) ParameterSliderView(parameter: patchBank.tempo, minMaxLabelToggle: .constant(false)) }.frame(width: 600.0) Spacer() } } } .sheet(isPresented: $dialogDisplayed) { SystemLevelPatchEditView(patchBank: patchBank, dialogDisplayed: $dialogDisplayed) } } `}

    Still, though, I should just be able to use Button, shouldn't I?

Add a Comment

Accepted Reply

As usually happens when I post a question to a forum, I more or less answered my own question. This problem is happening for any control which wraps NSButton or any class which extends NSButton, such as a Picker with a macOS default picker style which wraps NSPopUpButton.

SwiftUI Views which implement UI controls wrap the corresponding AppKit control -- Button wraps NSButton, Picker with default picker style wraps NSPopUpButton. The mechanism for this wrapping appears to be what's exposed to developers to wrap AppKit controls not covered by SwiftUI -- NSViewRepresentable. SwiftUI also appears to implement a default Coordinator instance to handle updates between the SwiftUI layer and the underlying AppKit control. This default Coordinator is mapped to the target field of the underlying NSButton.

When GarageBand loads the UI for an AudioUnit, it seems to try to add a selector to all of the NSButton instances in the UI to call a method called "lastClickedParameterView". Not surprisingly, this method is not implemented on the default SwiftUI Coordinator assigned to the target of any underlying NSButtons for a UI written using that framework. The selector refers to a method which doesn't exist, GarageBand doesn't handle the exception, and macOS aborts GarageBand. (GarageBand should handle this gracefully! But even more than that, GarageBand should not expect developers to implement methods which they couldn't possibly know about (it's not documented ANYWHERE), nor should it really try to attach functionality to code it knows nothing about!)

To mimic a Button, for the simplest cases, I was able to make my own out of a SwiftUI Group, but it's not that easy to do the same for a Picker with an underlying NSPopUpButton. It's a lot of work, which was frankly already done and provided by SwiftUI and AppKit, and I shouldn't have to reinvent the wheel.

Instead, I created a struct which implements NSViewRepresentable to wrap NSButton (and a separate struct to wrap NSPopUpButton), and of course, my own Coordinator classes to update SwiftUI bindings when the AppKit control updates. With my NSViewRepresentables in place, GarageBand does not crash!

The funny thing is, my Coordinators don't contain a method named "lastClickedParameterView"! Because I set the target of the underlying NSButton/NSPopUpButton to my Coordinator and the action to a selector referencing a method on my Coordinator, GarageBand either didn't or couldn't add its own selector to these NSButtons.

I'd really like to have any kind of hook in SwiftUI to the default Coordinator used in SwiftUI's wrappings of AppKit controls -- it would be great if I could extend the default Coordinator, add my own @objc public func's, and tell SwiftUI to use that class instead. Or pass a closure to a modifier, along with a "method name", so that any selector issued by an app hosting my UI would end up mapped to that closure.

Replies

As usually happens when I post a question to a forum, I more or less answered my own question. This problem is happening for any control which wraps NSButton or any class which extends NSButton, such as a Picker with a macOS default picker style which wraps NSPopUpButton.

SwiftUI Views which implement UI controls wrap the corresponding AppKit control -- Button wraps NSButton, Picker with default picker style wraps NSPopUpButton. The mechanism for this wrapping appears to be what's exposed to developers to wrap AppKit controls not covered by SwiftUI -- NSViewRepresentable. SwiftUI also appears to implement a default Coordinator instance to handle updates between the SwiftUI layer and the underlying AppKit control. This default Coordinator is mapped to the target field of the underlying NSButton.

When GarageBand loads the UI for an AudioUnit, it seems to try to add a selector to all of the NSButton instances in the UI to call a method called "lastClickedParameterView". Not surprisingly, this method is not implemented on the default SwiftUI Coordinator assigned to the target of any underlying NSButtons for a UI written using that framework. The selector refers to a method which doesn't exist, GarageBand doesn't handle the exception, and macOS aborts GarageBand. (GarageBand should handle this gracefully! But even more than that, GarageBand should not expect developers to implement methods which they couldn't possibly know about (it's not documented ANYWHERE), nor should it really try to attach functionality to code it knows nothing about!)

To mimic a Button, for the simplest cases, I was able to make my own out of a SwiftUI Group, but it's not that easy to do the same for a Picker with an underlying NSPopUpButton. It's a lot of work, which was frankly already done and provided by SwiftUI and AppKit, and I shouldn't have to reinvent the wheel.

Instead, I created a struct which implements NSViewRepresentable to wrap NSButton (and a separate struct to wrap NSPopUpButton), and of course, my own Coordinator classes to update SwiftUI bindings when the AppKit control updates. With my NSViewRepresentables in place, GarageBand does not crash!

The funny thing is, my Coordinators don't contain a method named "lastClickedParameterView"! Because I set the target of the underlying NSButton/NSPopUpButton to my Coordinator and the action to a selector referencing a method on my Coordinator, GarageBand either didn't or couldn't add its own selector to these NSButtons.

I'd really like to have any kind of hook in SwiftUI to the default Coordinator used in SwiftUI's wrappings of AppKit controls -- it would be great if I could extend the default Coordinator, add my own @objc public func's, and tell SwiftUI to use that class instead. Or pass a closure to a modifier, along with a "method name", so that any selector issued by an app hosting my UI would end up mapped to that closure.