TL;DR my singleton BLEManager managing Bluetooth communication keeps getting re-initialised (see console log). How should I prevent this?
Using Swift 5.9 for iOS in Xcode 15.1
My code finds multiple BT devices, and lists them for selection, also building an array of devices for reference.
Most code examples connect each device immediately. I am trying to connect later, when a specific device is selected and its View opens.
I pass the array index of the device to the individual Model to serve as a reference, hoping to pass that back to BLEManager to connect and do further communication.
After scanning has completed, the log message shows there is 1 device in it, so its not empty.
As soon as I try and pass a reference back to BLEManager, the app crashes saying the array reference is out of bounds. The log shows that BLEManager is being re-initialised, presumably clearing and emptying the array.
How should I be declaring the relationship to achieve this?
Console log showing single device found:
ContentView init
BLEManager init
didDiscover id: 39D43C90-F585-792A-5BD6-8749BA0B5385
In didDiscover devices count is 1
stopScanning
After stopScanning devices count is 1
<-- selection made here
DeviceModel init to device id: 0
BLEManager init
BLEManager connectToDevice id: 0
devices is empty
Swift/ContiguousArrayBuffer.swift:600: Fatal error: Index out of range
2023-12-28 11:45:55.149419+0000 BlueTest1[20773:1824795] Swift/ContiguousArrayBuffer.swift:600: Fatal error: Index out of range
BlueTest1App.swift
import SwiftUI
@main
struct BlueTest1App: App {
var body: some Scene {
WindowGroup {
ContentView(bleManager: BLEManager())
}
}
}
ContentView.swift
import SwiftUI
struct TextLine: View {
@State var dev: Device
var body: some View {
HStack {
Text(dev.name).padding()
Spacer()
Text(String(dev.rssi)).padding()
}
}
}
struct PeripheralLineView: View {
@State var devi: Device
var body: some View {
NavigationLink(destination: DeviceView(device: DeviceModel(listIndex: devi.id))) {
TextLine(dev: devi)
}
}
}
struct ContentView: View {
@StateObject var bleManager = BLEManager.shared
init(bleManager: @autoclosure @escaping () -> BLEManager) {
_bleManager = StateObject(wrappedValue: bleManager())
print("ContentView init")
}
var body: some View {
VStack (spacing: 10) {
if !bleManager.isSwitchedOn {
Text("Bluetooth is OFF").foregroundColor(.red)
Text("Please enable").foregroundColor(.red)
}
else {
HStack {
Spacer()
if !bleManager.isScanning {Button(action: self.bleManager.startScanning){ Text("Scan ")
}
} else { Text("Scanning")
}
}
NavigationView {
List(bleManager.devices) { peripheral in
PeripheralLineView(devi: peripheral)
}.frame(height: 300)
}
}
}
}
}
//@available(iOS 15.0, *)
struct DeviceView: View {
var device: DeviceModel
var body: some View {
ZStack {
VStack {
Text("Data Window")
Text("Parameters")
}
}.onAppear(perform: {
device.setUpModel()
})
}
}
BLEManager.swift
import Foundation
import CoreBluetooth
struct Device: Identifiable {
let id: Int
let name: String
let rssi: Int
let peri: CBPeripheral
}
class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate {
static let shared: BLEManager = {
let instance = BLEManager()
return instance
}()
var BleManager = BLEManager.self
var centralBE: CBCentralManager!
@Published var isSwitchedOn = false
@Published var isScanning = false
var devices = [Device]()
var deviceIds = [UUID]()
private var activePeripheral: CBPeripheral!
override init() {
super.init()
print(" BLEManager init")
centralBE = CBCentralManager(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
isSwitchedOn = true
}
else { isSwitchedOn = false }
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String {
if !deviceIds.contains(peripheral.identifier) {
print("didDiscover id: \(peripheral.identifier)")
deviceIds.append(peripheral.identifier)
let newPeripheral = Device(id: devices.count, name: name, rssi: RSSI.intValue, peri: peripheral)
devices.append(newPeripheral)
print("didDiscover devices count now \(devices.count)")
}
}
}
/// save as activePeripheral and connect
func connectToDevice(to index: Int) {
print("BLEManager connectToDevice id: \(index)")
if devices.isEmpty {print ("devices is empty")}
activePeripheral = devices[index].peri
activePeripheral.delegate = self
centralBE.connect(activePeripheral, options: nil)
}
func startScanning() {
centralBE.scanForPeripherals(withServices: nil, options: nil)
isScanning = true
// Stop scan after 5.0 seconds
let _: Timer = Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(stopScanning), userInfo: nil, repeats: false)
}
@objc func stopScanning() { // need @objc for above Timer selector
print("stopScanning")
centralBE.stopScan()
isScanning = false
print("After stopScanning devices count is \(devices.count)")
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { }
func disconnect(peripheral: Int) { }
func discoverServices(peripheral: CBPeripheral) { }
func discoverCharacteristics(peripheral: CBPeripheral) { }
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { }
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { }
}
DeviceModel.swift
import Foundation
import CoreBluetooth
class DeviceModel: BLEManager {
var index: Int //CBPeripheral position in found array
init(listIndex: Int) {
index = listIndex
print("DeviceModel init to device id: \(index)")
}
func setUpModel() {
connectToDevice(to: index)
}
}
Post
Replies
Boosts
Views
Activity
This error is generated at the first case statement in my example code.
Type 'Binding' has no member 'atype1'
If I change the order of the cases in the switch statement, it is always the first one which gives the error.
In my code, if you delete the value of that first switch line, and type it in again, as soon as you have entered 'case .' the drop down menu will offer suggestions and you can see the atype1 & atype2 near the top of the list.
What am I doing wrong?
import SwiftUI
enum AType: CaseIterable {
case atype1
case atype2
case unknown // default
}
struct Peripheral: Identifiable {
let id: Int
let name: String
var type: AType = .unknown
}
struct ContentView: View {
@State private var peri = Peripheral(id: 1, name: "A")
var body: some View {
switch $peri.type {
case .atype1:
Text("Hello, $peri.name!")
case .atype2:
Text("Goodbye, $peri.name!")
case .unknown:
Text("Oh no!")
}
.padding()
}
}
#Preview() {
ContentView()
}
In a small project under Xcode source control, there is an unknown file (BlePeripheral.swift) which I can't find or delete. I did add it at one stage, but I thought I had deleted all references to it.
I have done Clean Build Folder, closed the project, and closed Xcode in the hope of resolving.
This is the source control panel showing the unwanted file in green. Right clicking on it does not have an option to Delete.
This is the main Navigation panel which doesn't show it (so no option to delete it). It was in the Model folder.
This is the search panel hoping to find it referenced somewhere
and this is the Finder view of the project folder. Its not in the Assets.xcassets or Preview Content folders.
I have an external disk which I used to boot from, but I can't remember which version of macOS is installed.
Where do I look in the directories to determine which version is installed?
I am not booted into it, and I don't want to boot in yet, so can't just look in About this Mac.
The System folder itself is dated 22 March 2022 so it's quite recent.