Post

Replies

Boosts

Views

Activity

Reply to SwiftUI crash: Simultaneous accesses to X, but modification requires exclusive access.
@olonsky Thanks for the example. I think the root cause is that in our code bases the navigation is changed in two different runloops, giving SwiftUI conflicting information about the desired navigation state. The fix is to make sure that the navigation for all views is derived from a single value (nested enum or an array) that is changed in a single step. btw: I don't understand why you need to defer changing the path using DispatchQueue.main.async. By removing this hack, the crash is gone. .onReceive(coordinator.$path) { value in // DispatchQueue.main.async { path = value // } }
Oct ’23
Reply to SwiftUI crash: Simultaneous accesses to X, but modification requires exclusive access.
Ok, probably this is a bug in SwiftUI. We created an example that allows reproducing the crash on an iOS 17 device. We filed feedback FB13238006 about this. Feel free to duplicate when you're affected by this. import SwiftUI /// SwiftUI Toolbar crasher /// ======================= /// /// Steps to reproduce /// ------------------ /// * launch this app on a physical device running iOS 17 (any stable or beta release). /// * Tap the center button ("Go to view 2") /// * Tap the center button ("Go to view 3") /// * Tap the center button ("Go to view 4") /// * Tap the center button ("Go to view 5") /// * Tap the center button ("Pop to root πŸ’£") /// /// Expected result /// --------------- /// * The app navigates back to the root screen. /// /// Actual result /// ------------- /// * The app crashes in 30-50% of the attempts. /// /// class ToolbarCrasher: ObservableObject { @Published var navTree: NavigationTree? = nil } enum NavigationTree { case one(One?) var one: One? { switch self { case let .one(one): one } } var isOne: Bool { switch self { case .one: true } } enum One { case two(Two?) var two: Two? { switch self { case let .two(two): two } } var isTwo: Bool { switch self { case .two: true } } enum Two { case three(Three?) var three: Three? { switch self { case let .three(three): three } } var isThree: Bool { switch self { case .three: true } } enum Three { case four var isFour: Bool { switch self { case .four: true } } } } } } struct ContentView: View { @StateObject var toolbarCrasher: ToolbarCrasher = .init() var body: some View { NavigationStack { VStack { Text("Root View") Button( action: { toolbarCrasher.navTree = .one(nil) }, label: { Text("Go to view 2") } ) } .navigationDestination( isPresented: .init( get: { toolbarCrasher.navTree?.isOne ?? false }, set: { isForward in if isForward { toolbarCrasher.navTree = nil } } ), destination: { ContentView2(toolbarCrasher: toolbarCrasher) } ) .padding() .toolbar { ToolbarItem(placement: .topBarTrailing, content: { Button( action: { toolbarCrasher.navTree = nil }, label: { Text("Pop to root πŸ’£") } ) }) } } } } struct ContentView2: View { @ObservedObject var toolbarCrasher: ToolbarCrasher var body: some View { VStack { Text("View 2") Button( action: { toolbarCrasher.navTree = .one(.two(nil)) }, label: { Text("Go to view 3") } ) } .toolbar { ToolbarItem(placement: .topBarTrailing, content: { Button( action: { toolbarCrasher.navTree = nil }, label: { Text("Pop to root πŸ’£") } ) }) } .navigationDestination( isPresented: .init( get: { toolbarCrasher.navTree?.one?.isTwo ?? false }, set: { isForward in if isForward { toolbarCrasher.navTree = .one(nil) } } ), destination: { ContentView3(toolbarCrasher: toolbarCrasher) } ) .padding() } } struct ContentView3: View { @ObservedObject var toolbarCrasher: ToolbarCrasher var body: some View { VStack { Text("View 3") Button( action: { toolbarCrasher.navTree = .one(.two(.three(nil))) }, label: { Text("Go to view 4") } ) } .toolbar { ToolbarItem(placement: .topBarTrailing, content: { Button( action: { toolbarCrasher.navTree = nil }, label: { Text("Pop to root πŸ’£") } ) }) } .navigationDestination( isPresented: .init( get: { toolbarCrasher.navTree?.one?.two?.isThree ?? false }, set: { isForward in if isForward { toolbarCrasher.navTree = .one(.two(nil)) } } ), destination: { ContentView4(state: toolbarCrasher) } ) .padding() } } struct ContentView4: View { @ObservedObject var state: ToolbarCrasher var body: some View { VStack { Text("View 4") Button( action: { state.navTree = .one(.two(.three(.four))) }, label: { Text("Go to view 5") } ) } .toolbar { ToolbarItem(placement: .topBarTrailing, content: { Button( action: { state.navTree = nil }, label: { Text("Pop to root πŸ’£") } ) }) } .navigationDestination( isPresented: .init( get: { state.navTree?.one?.two?.three?.isFour ?? false }, set: { isForward in if isForward { state.navTree = .one(.two(.three(nil))) } } ), destination: { ContentView5(toolbarCrasher: state) } ) .padding() } } struct ContentView5: View { @ObservedObject var toolbarCrasher: ToolbarCrasher var body: some View { Text("View 5") Button( action: { toolbarCrasher.navTree = nil }, label: { Text("Pop to root πŸ’£") } ) } } #Preview { ContentView() }
Oct ’23
Reply to AccessorySetupKit: Usage of manufacturer data blob
I'm sorry, but I have to ask again to make sure I understand the issue. When we use CoreBluetooth's CBCentralManager to scan for peripherals, we'll see this data in the advertisementData dictionary (CBAdvertisementDataManufacturerDataKey): 0xd2 0x0a 0xb1 0xb2 0xb3 0xb4 0xb5 0xb6 0xb7 0xb8 I understand that AccessorySetupKit expects a different byte order and that the ASDiscoveryDescriptor should be set up as follows let descriptor = ASDiscoveryDescriptor() descriptor.bluetoothServiceUUID = CBUUID(string: "ce1eb45c-1bd2-45be-8163-acc88be94cb2") descriptor.bluetoothCompanyIdentifier = .init(0x0ad2) descriptor.bluetoothManufacturerDataBlob = Data([0xb2, 0xb1, 0xb4, 0xb3, 0xb6, 0xb5, 0xb8, 0xb7]) // ???? descriptor.bluetoothManufacturerDataMask = Data([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]) Could you please confirm if this setup is correct? If not, I would greatly appreciate any corrections or guidance you could provide. I find the differing byte orders between the advertisementData dictionary and the ASDiscoveryDescriptor a bit confusing. If the above setup is correct, I will proceed to file feedback with logs, as we are currently unable to detect our accessory using AccessorySetupKit.
Jun ’24
Reply to AccessorySetupKit: Usage of manufacturer data blob
Hello! We used LightBlue on Android to capture the complete BLE advertisement for two products we try to pair with AccessorySetupKit. I masked the local name of product 2, because it's not released yet. Product 1 has this BLE advertisement: 0x0B097461747773325F626C6508FFD20A00350000001107B24CE98BC8AC6381BE45D21B5CB41ECE0000000000000000000000000000000000000000000000 The manufacturer data as seen from CoreBluetooth (CBAdvertisementDataManufacturerDataKey) is 0xd20a0035000000 This is the desciptor we are using: let descriptor1 = ASDiscoveryDescriptor() descriptor1.bluetoothServiceUUID = CBUUID(string: "CE1EB45C-1BD2-45BE-8163-ACC88BE94CB2") descriptor1.bluetoothManufacturerDataBlob = Data([0x00, 0x35, 0x00, 0x00, 0x00]) descriptor1.bluetoothManufacturerDataMask = Data([0xff, 0xff, 0xff, 0xff, 0xff]) descriptor1.bluetoothCompanyIdentifier = .init(0x0ad2) Product 2 has this BLE advertisement: 0x02010A1107B24CE98BC8AC6381BE45D21B5CB41ECE08FFOAD2003F1300000909**************0000000000000000000000000000000000000000000000 The manufacturer data as seen from CoreBluetooth (CBAdvertisementDataManufacturerDataKey) is 0xd20affd20a0000000000 This is the desciptor we are using: let descriptor2 = ASDiscoveryDescriptor() descriptor2.bluetoothServiceUUID = CBUUID(string: "CE1EB45C-1BD2-45BE-8163-ACC88BE94CB2") descriptor2.bluetoothManufacturerDataBlob = Data([0xFF, 0xD2, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00]) descriptor2.bluetoothManufacturerDataMask = Data([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]) descriptor2.bluetoothCompanyIdentifier = .init(0x0ad2) Both products can be found with CoreBluetooth, however, both are not visible when using the AccessorySetupKit. Is there anything unexpected the advertisement? Do we need to change anything in the desciptors to match the advertisements?
Jun ’24
Reply to AccessorySetupKit: Usage of manufacturer data blob
I filed feedbacks for different advertisements used by 3 different products: FB14052487 FB14051395 FB14051767 Bluetooth and ASK logging profiles are installed and I attached the sysdiagnose to each. About the lenght of the advertisements: I assume we're using extended advertising, I'll check with the hardware people and update you. The missing discoverability flag seem like a bug. I can try to have this fixed, but unfortunately we cannot fix this for already released products. This advertisement contains the "General Discoverable" flag and is also not discovered by ASK: 0x02010A1107B24CE98BC8AC6381BE45D21B5CB41ECE08FFD2OA003F1300000909**************0000000000000000000000000000000000000000000000 The not properly configured ASDiscoveryDescriptor was caused by pasting the wrong code to the message. I was using your suggestion, unfortunately without success. The info.plist looks like this: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>NSAccessorySetupBluetoothServices</key> <array> <string>CE1EB45C-1BD2-45BE-8163-ACC88BE94CB2</string> </array> <key>NSAccessorySetupKitSupports</key> <array> <string>Bluetooth</string> </array> </dict> </plist>
Jun ’24