When wrapping CABTMIDICentralViewController in a UIViewControllerRepresentable and presenting modally, I experience layout crashes since iOS 27. Does anyone have a workaround?
import SwiftUI
import CoreAudioKit
struct BluetoothButton: View {
@State var showsBluetooth = false
let title = "Check for Bluetooth MIDI Devices"
var body: some View {
#if os(macOS)
Button {
let wc = CABTLEMIDIWindowController()
wc.showWindow(nil)
} label: { label }
#else
Button {
showsBluetooth = true
} label: { label }
.sheet(isPresented: $showsBluetooth) {
BluetoothCentralView()
}
#endif
}
var label: some View {
Text(title)
}
}
#if os(iOS)
struct BluetoothCentralView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> CABTMIDICentralViewController {
return CABTMIDICentralViewController()
}
func updateUIViewController(_ uiViewController: CABTMIDICentralViewController, context: Context) {
}
}
#endif
Thanks for posting the code, which reproduced the crash on the first try. Giving the view controller its table view before presenting it avoids the crash in my testing. Details and code are below.
On an iPhone running iOS 27.0 (24A427), presenting CABTMIDICentralViewController crashes with this exception:
*** Terminating app due to uncaught exception 'CALayerInvalidGeometry', reason: 'CALayer bounds contains NaN: [0 0; 440 nan]. Layer: <CALayer: ...; delegate = <UITableView: ...>
The exception comes from -[CABTMIDICentralViewController loadView], which creates its table view with a height that isn't a number. It happens with your sheet, and also when I present the view controller with the UIKit present(_:animated:) method, without SwiftUI. On an iPhone running iOS 26.6.2, the same app presents the view controller, and it searches for MIDI devices.
The CABTMIDICentralViewController documentation says to "either present it modally or push it onto a UINavigationController view controller." It adds that "No other configuration of the object is necessary." Your code does exactly that. Please file a report in Feedback Assistant with your sample and the crash log, and post the FB number here. Just before the exception, the console logs "-[UIApplication statusBarHeightForOrientation:] API has been deprecated and is a no-op on 27.0 and later." Please include that line in the report too.
The workaround builds on documented behavior. The view documentation says that if you access the property "when its value is nil, the view controller automatically calls the loadView() method." When you set the table view first, the view controller's own loadView() doesn't run:
func makeUIViewController(context: Context) -> CABTMIDICentralViewController {
let controller = CABTMIDICentralViewController()
if #available(iOS 27, *) {
controller.tableView = UITableView(frame: .zero, style: .grouped)
}
return controller
}
With that change on iOS 27.0, the view controller appears and searches for MIDI devices. I didn't have a Bluetooth MIDI peripheral to test discovery and pairing, so please confirm those with your hardware. This depends on an undocumented detail: the view controller doesn't need its own loadView(). The #available check keeps the standard behavior on earlier releases.
The workaround in the earlier reply replaces -[UIView initWithFrame:] for every view in the app. This one changes only the Bluetooth MIDI view controller.
If building your own discovery interface is an option, Core MIDI documents another route. MIDI Bluetooth explains that since iOS 16, "the system automatically reconnects Bluetooth Low Energy (BLE) MIDI peripherals when powered on, if the device supports pairing." It also describes how to connect other peripherals with Core Bluetooth and MIDIBluetoothDriverActivateAllConnections().