Post

Replies

Boosts

Views

Activity

Reply to GarageBand aborts when clicking on any SwiftUI button in AudioUnit plugin
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?
Jun ’21
Reply to GarageBand aborts when clicking on any SwiftUI button in AudioUnit plugin
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.
Jul ’21
Reply to Xcode 12 Framework: 'Double-quoted include in framework header, expected angle-bracketed instead'
This is not just an issue with Cocoapods projects, and it's a compile error instead of a warning in Xcode 15.0.1. I was having this problem in a native macos application, where one of my application modules is a framework, and the framework module has a C header file referenced by two other C header files, also in the framework. In my codebase, the framework module is named IslepopAUFramework, and the three header files (all at the root level of the framework project) were coded as such: islepop_editapi_types.h: #ifndef islepop_editapi_types_h #define islepop_editapi_types_h typedef enum { UNKNOWN, EDIT_AUDIO_CONFIGURATION, GET_PATCH_BANK, SAVE_PATCH_BANK, NEW_PATCH_BANK, LOAD_PATCH_BANK, NEW_PATCH, ... islepop_external_bindings.h: #ifndef ISLEPOP_EXTERNAL_BINDINGS_H #define ISLEPOP_EXTERNAL_BINDINGS_H #import "islepop_editapi_types.h" //Edit API void fire_the_engine(); void shut_it_down(); RustEditAPIResponse* send_command(RustEditAPICommand* command); void free_response(RustEditAPIResponse* response); ... #endif islepop_midi_1_0_bindings.h: #ifndef islepop_midi_1_0_bindings_h #define islepop_midi_1_0_bindings_h #import "islepop_editapi_types.h" //CRUD operations for MIDI subscriptions.. or at least the CR and D: RustEditAPIResponse* add_midi_subscription(int32_t patch_id, int32_t patch_unit_id, int32_t parameter_id, double_t range_low, double_t range_high); RustEditAPIResponse* remove_midi_subscription(int32_t subscription_id, uint8_t message_type, uint8_t message_subtype); RustEditAPIResponse* send_command_to_midi(RustEditAPICommand* command); void free_midi_response(RustEditAPIResponse* response); #endif As noted at the bottom of this post (https://developer.apple.com/forums/thread/69606), I had to add all of my C header files to the Headers section of the Build Phases tab of my framework target. I also had to change the #import statements in islepop_external_bindings.h and islepop_midi_1_0_bindings.h to angle-bracketed imports, as such -- notice that the angle brackets include the framework name, even though all of these header files are in the same place, at the root of IslepopAUFramework: #import <IslepopAUFramework/islepop_editapi_types.h> With these two changes, the error requiring angle-bracketed includes is fixed, and the compiler can actually find the header files.
Nov ’23