Scan for BLE devices and present them in an UITabl

2019-07-14 18:42发布

问题:

I try to find a way to scan for BLE devices and present them in an UITableView. The scan, connect, read and write functionality for BLE devices is clear and works! So my questions are focused on the interaction between the 'ScanTableView' and 'BletoothManager' class.

These are my two classes:

//  ScanTableView.swift

import UIKit

class ScanTableView: UITableViewController {

    @IBOutlet var scanTableView: UITableView!

    var bluetoothManager = BluetoothManager?()
    var tableViewScanTime = 5
    var timer1: NSTimer!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.refreshControl!.addTarget(self, action: "refresh", forControlEvents: UIControlEvents.ValueChanged)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if let _ = bluetoothManager?.peripheralArray.count {
            return bluetoothManager!.peripheralArray.count
        }
        else {
            return 0
        }
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = scanTableView.dequeueReusableCellWithIdentifier("scanCell",forIndexPath: indexPath)
        cell.textLabel!.text = bluetoothManager!.peripheralArray[indexPath.row].name
        cell.detailTextLabel!.text = bluetoothManager!.peripheralArray[indexPath.row].RSSI
        return cell
    }

    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        bluetoothManager!.selectedPeripheral = bluetoothManager!.peripheralArray[indexPath.row]
        bluetoothManager!.connectPeripheral(bluetoothManager!.selectedPeripheral!)
    }

    func refresh() {
        scanTableView.userInteractionEnabled = false
        timer1 = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "scanTableViewRefresh", userInfo: nil, repeats: true)
        bluetoothManager = BluetoothManager()
    }

    func scanTableViewRefresh() {
        scanTableView.reloadData()
        tableViewScanTime--

        if tableViewScanTime <= 0 {
            timer1.invalidate()
            bluetoothManager!.CBmanager.stopScan()
            print("StopScan")
            tableViewScanTime = 5
            bluetoothManager!.peripheralArray.sortInPlace({$0.RSSI < $1.RSSI})
            self.refreshControl!.endRefreshing()
            self.scanTableView.userInteractionEnabled = true
        }
    }
}

//  BluetoothManager.swift

import UIKit
import CoreBluetooth

class BluetoothManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {

    struct BluetoothPeripheral {
        let name: String
        let UUID: String
        let RSSI: String
        let peripheral: CBPeripheral

        init(name: String, UUID: String, RSSI: NSNumber, peripheral: CBPeripheral) {
            self.name = "\(name)"
            self.UUID = "\(UUID)"
            self.RSSI = "\(RSSI)"
            self.peripheral = peripheral
        }
    }

    let DEVICE_NAME:String! = "TEST"

    //Creat an instance of ScanTableView Class
    var scanTableView: ScanTableView()


    var peripheralArray: [BluetoothPeripheral] = []
    var selectedPeripheral: BluetoothPeripheral?
    var characteristicArray: [CBCharacteristic] = []
    var CBmanager: CBCentralManager = CBCentralManager()
    var measurementValue: [[AnyObject?]] = [[]]

    //Basic functions
    override init() {
        super.init()
        CBmanager = CBCentralManager(delegate: self, queue: nil)
    }

    func connectPeripheral(selectedPeripheral: BluetoothPeripheral) {
        CBmanager.connectPeripheral(selectedPeripheral.peripheral, options: nil)
    }

    func disconnectPeripheral(selectedPeripheral: BluetoothPeripheral) {
        for characteristic in characteristicArray {
            selectedPeripheral.peripheral.setNotifyValue(false, forCharacteristic: characteristic as CBCharacteristic)
        }
        CBmanager.cancelPeripheralConnection(selectedPeripheral.peripheral)
    }

    func ScanForPeripherals() {
        CBmanager.scanForPeripheralsWithServices(nil, options: nil)
        print("Scanning")
    }

    func centralManagerDidUpdateState(central: CBCentralManager) {
        switch(central.state) {
        case .PoweredOn:
            CBmanager.scanForPeripheralsWithServices(nil, options: nil)
            print("scan")
        case .PoweredOff, .Resetting, .Unauthorized, .Unsupported, .Unknown:
            peripheralArray.removeAll()

            //This invokes an exception
            //scanTableView.scanTableView.reloadData()

            print("NO BLE!")
        }
    }

    func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
        let UUID = "\(peripheral.identifier)".substringFromIndex("\(peripheral.identifier)".startIndex.advancedBy(31))
        if let peripheralName = peripheral.name {
            if peripheralName.containsString(DEVICE_NAME) {
                peripheralArray.append(BluetoothPeripheral(name: peripheral.name!, UUID: UUID, RSSI: RSSI, peripheral: peripheral))
                print(peripheralArray)
            }
        }
    }

    func centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) {
        print("Connected")
        measurementValue.removeAll()
        peripheral.delegate = self
        selectedPeripheral!.peripheral.discoverServices(nil)
    }

    func centralManager(central: CBCentralManager, didFailToConnectPeripheral peripheral: CBPeripheral, error: NSError?) {
        print("Fail")
    }

    func centralManager(central: CBCentralManager, willRestoreState dict: [String : AnyObject]) {
        print("Restore")
    }

    func centralManager(central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: NSError?) {
        print("Disconnected")
    }

    func peripheral(peripheral: CBPeripheral, didDiscoverServices error: NSError?) {
        for service in peripheral.services! {
            peripheral.discoverCharacteristics(nil, forService: service)
        }
    }

    func peripheral(peripheral: CBPeripheral, didDiscoverCharacteristicsForService service: CBService, error: NSError?) {
        for characteristic in service.characteristics as [CBCharacteristic]!{
            if characteristic.properties.contains(CBCharacteristicProperties.Notify) {
                peripheral.discoverDescriptorsForCharacteristic(characteristic)
                peripheral.setNotifyValue(true, forCharacteristic: characteristic)
            }
        }
    }

    func peripheral(peripheral: CBPeripheral, didUpdateNotificationStateForCharacteristic characteristic: CBCharacteristic, error: NSError?) {
        if characteristic.isNotifying {
            characteristicArray.append(characteristic as CBCharacteristic)
            peripheral.readValueForCharacteristic(characteristic)
        }
    }

    func peripheral(peripheral: CBPeripheral, didUpdateValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) {
        //Store new characteristic values
    }
}

Now my questions:

The shown code works but I'm not able to interact between the two classes. For example I would like to reload my opened ScanTableView from my BluetoothManager class. That's not possible... every time when I try this, I get an exception that I would unwrap an optional. Why? Are there any differences between 'normal' classes and the classes shown in the GUI (UITableView, UIView...)? I documented the exception line...

It would be really nice, if anyone could explain me what to do in such situations :).

I'm happy for any suggestions or improvements!

回答1:

Like @paulw11 said, I had to creat a delegate protocol:

protocol BluetoothDelegate: class {

    func ReloadView()
}

This 'ReloadView' method is declared in my ScanTableView class:

func ReloadView() {
    scanTableView.reloadData()
}

Now, I had to do some additional:

  1. Add the BluetoothDelegate to ScanTableView class
  2. Declare a variable 'weak var delegate: BluetoothDelegate?' in BluetoothManager class
  3. call the delegate method with 'delegate?.ReloadView()' at the wished point in BluetoothManager class
  4. Activate the delegate in ScanTableView by using 'bluetoothManager?.delegate = self'

That's it!