I have implemented CBManagerDelegate and can print the status successfully, thanks to this answer . But I want to store this state in a variable and use in my post API. How to proceed?
Tried to store in a variable normally but failed. Protocols might work but I am not sure.
class ViewController: UIViewController, CBCentralManagerDelegate {
var isBTTurnedOn: Bool?
var manager: CBCentralManager!
public func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOn:
print("Bluetooth is ON.")
isBTTurnedOn = true
break
case .poweredOff:
print("Bluetooth is Off.")
isBTTurnedOn = false
#unknown default:
break
}
}
//IN THE VIEWDIDLOAD() ->
override func viewDidLoad() {
super.viewDidLoad()
manager = CBCentralManager()
manager.delegate = self
}
// IN VIEWWILLAPPEAR()
print("Bt status is \(isBTTurnedOn)")) // Getting nil here
Related
Is there any way in iOS to detect if Wifi & Bluetooth is off so that I can show alert to the user to enable it from settings.
Note: I am interested in the hardware setting is on/off not the internet is reachable or not.
You can try the following for network connection availability
func isInternetAvailable() -> Bool {
var isNetworkReachable = false
if let reachability: Reachability = try? Reachability() {
isNetworkReachable = reachability.connection != .unavailable
}
return isNetworkReachable
}
for BLE
class BLEManager: NSObject, CBCentralManagerDelegate {
var bleManagerDelegate: BLEManagerDelegate?
var bluetoothManager: CBCentralManager?
private override init() {
super.init()
bleManager = CBCentralManager(delegate: self, queue:nil,
options:[CBCentralManagerOptionShowPowerAlertKey: true])
}
//CBCentralManagerDelegate method invoked
func centralManagerDidUpdateState(_ central: CBCentralManager) {
var bleStateError: BLEError = .kBLEStateUnknown
switch central.state {
case .poweredOff:
// powered Off
case .poweredOn:
// powered On
case .unknown:
//
case .resetting:
//
case .unsupported:
//
case .unauthorized:
//
}
}
}
//For Wifi
Use SwiftReachability https://github.com/ashleymills/Reachability.swift
//declare this property where it won't go out of scope relative to your listener
let reachability = try! Reachability()
//declare this inside of viewWillAppear
NotificationCenter.default.addObserver(self, selector: #selector(reachabilityChanged(note:)), name: .reachabilityChanged, object: reachability)
do{
try reachability.startNotifier()
}catch{
print("could not start reachability notifier")
}
#objc func reachabilityChanged(note: Notification) {
let reachability = note.object as! Reachability
switch reachability.connection {
case .wifi:
print("Reachable via WiFi")
case .cellular:
print("Reachable via Cellular")
case .unavailable:
print("Network not reachable")
}
}
stopping notifications
reachability.stopNotifier()
NotificationCenter.default.removeObserver(self, name: .reachabilityChanged, object: reachability)
// For bluetooth
import CoreBluetooth
var myBTManager = CBPeripheralManager(delegate: self, queue: nil, options: nil)
//BT Manager
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager)
{
if peripheral.state == .poweredOn {
// myBTManager!.startAdvertising(_broadcastBeaconDict)
} else if peripheral.state == .poweredOff {
// myBTManager!.stopAdvertising()
} else if peripheral.state == .unsupported
{
} else if peripheral.state == .unauthorized
{
}
}
WorkAround:
I am creating a utility class let's say BLEScanManager, which is responsible to scan nearby BLE devices. The only job of the utility class is to scan BLE devices and make a list of it.
The other classes can create an object of this BLEScanManager and get an array of BLE devices, like [Bluetooth] (here Bluetooth is a custom modal class).
Now to scan BLE devices, I have created the extension of BLEScanManager in the same class and override its delegate methods like below:
import UIKit
import CoreBluetooth
protocol BLEScanDelegate {
func reloadDeviceList()
}
internal class BLEScanManager: NSObject {
private var centralManager: CBCentralManager?
var devices : [Bluetooth] = []
var delegate: BLEScanDelegate?
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: .main)
}
// MARK:- Custom methods
func isScanning() -> Bool {
return centralManager?.isScanning ?? false
}
func stopScanning() {
centralManager?.stopScan()
}
func startScanning() {
devices.removeAll()
let options: [String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey:
NSNumber(value: false)]
centralManager?.scanForPeripherals(withServices: nil, options: options)
}
}
extension BLEScanManager : CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .unknown: print("central.state is .unknown")
case .resetting: print("central.state is .resetting")
case .unsupported: print("central.state is .unsupported")
case .unauthorized: print("central.state is .unauthorized")
case .poweredOff: print("central.state is .poweredOff")
case .poweredOn: print("central.state is .poweredOn")
self.startScanning()
#unknown default: fatalError("unknow state")
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print("Discovered \(peripheral)")
var bluetooth = Bluetooth()
bluetooth.name = peripheral.name
bluetooth.identifier = peripheral.identifier.uuidString
bluetooth.state = peripheral.state.rawValue
bluetooth.advertisementData = advertisementData
let power = advertisementData[CBAdvertisementDataTxPowerLevelKey] as? Double
let value: Double = pow(10, ((power ?? 0 - Double(truncating: RSSI))/20))
bluetooth.signalStrength = String(describing: value.round)
// Do not add duplicate device
let fitlerArray : [Bluetooth] = devices.filter { $0.identifier == bluetooth.identifier}
if fitlerArray.count == 0 {
devices.append(bluetooth)
}
self.delegate?.reloadDeviceList()
}
}
The thing here is these methods are exposed in the other classes too.
For example:
I have created an object of BLEScanManager in other class BLEListViewController to show a list of BLE device in UITableView.
class BLEListViewController: UITableViewController {
var scanManager: BLEScanManager!
}
I can access CBCentralManagerDelegate delegate methods in BLEListViewController class using an object scanManager.
Like below,
self.scanManager.centralManager?(<#T##central: CBCentralManager##CBCentralManager#>, didConnect: <#T##CBPeripheral#>)
This should expose the internal utility delegate methods to the outside world.
Question is how to stop exposing these delegates?
Please note that, if I use internal keyword it only hides that specific method. But it still allows accessing all other CBCentralManagerDelegate methods.
There’s nothing you can do about this. It is a problem with all optional protocol methods. The trouble is that optional protocol methods are an Objective C feature. You cannot hide such a method as private, because now Objective C cannot see it and so the method will never be discovered and called. It’s a flaw in the way Swift interfaces with Objective C. You just have to live with it.
What you need to do, is to hide behind a protocol - see the following example.
protocol Exposed {
func run()
}
struct Bluetooth {
func hidden() {
print("This method is hidden - ish")
}
}
extension Bluetooth: Exposed {
func run() {
print("this method is exposed")
}
}
struct BluetoothScanManager {
static func scan() -> [Exposed] {
return [Bluetooth(), Bluetooth()]
}
}
Now, when you run BluetoothScanManager you will get a list of exposed only with the run() method available, but behind the code it is a Bluetooth
for exposed in BluetoothScanManager.scan() {
exposed.run()
exposed.hidden() // not possible
}
Your only way to do this (which also is the correct way in my opinion) is to create a new private object inside the BLEScanManager class that implements the CBCentralManagerDelegate.
Than you can pass this object to the CBCentralManager init method.
If you then need to comunicate from this object to the BLEScanManager you can simply use another delegate with a private protocol so it doesn't show those methods outside of the class.
It would look something like this:
import UIKit
import CoreBluetooth
protocol BLEScanDelegate {
func reloadDeviceList()
}
internal class BLEScanManager: NSObject {
private var centralManager: CBCentralManager?
var devices : [Bluetooth] = []
var delegate: BLEScanDelegate?
let centralManagerDelegate = MyCentralManagerDelegateImplementation()
override init() {
super.init()
centralManagerDelegate.delegate = self
centralManager = CBCentralManager(delegate: centralManagerDelegate, queue: .main)
}
// MARK:- Custom methods
func isScanning() -> Bool {
return centralManager?.isScanning ?? false
}
func stopScanning() {
centralManager?.stopScan()
}
func startScanning() {
devices.removeAll()
let options: [String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey:
NSNumber(value: false)]
centralManager?.scanForPeripherals(withServices: nil, options: options)
}
}
private extension BLEScanManager: CentralManagerEventsDelegate {
func didUpdateState(){
// Here goes the switch with the startScanning
}
func didDiscoverPeripheral(...) {
// Here goes the logic on new peripheral and the call on self.delegate?.reloadDeviceList
}
}
internal protocol CentralManagerEventsDelegate: class {
func didUpdateState()
func didDiscoverPeripheral(...)
}
internal class MyCentralManagerDelegateImplementation: NSObject, CBCentralManagerDelegate {
weak var delegate: CentralManagerEventsDelegate?
func centralManagerDidUpdateState(_ central: CBCentralManager) {
self.delegate?.didUpdateState()
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
self.delegate?.didDiscoverPeripheral(actualParams)
}
}
I enabled to “Uses Bluetooth LE accessories” and “Acts as a Bluetooth LE accessory” in “Background Modes” settings, and these are added in Info.plist.
and also I allowed my app to access “Bluetooth sharing”.
And my app does scan BLE peripherals well on foreground mode,
but when it enter background mode, it doesn’t scan anything no more. If it enter foreground mode again, it does scan well again.
More specifically, centralManagerDidUpdateState(_ central: CBCentralManager) always does work well on any modes. but didDiscover doesn't work on background mode.
I want to BLE scanning does work well on any modes and I think I have done everything I can.
Relevant Code
BLEManager.swift
import UIKit
import CoreBluetooth
final class BLEManager: NSObject, CBCentralManagerDelegate, CBPeripheralManagerDelegate {
static let shared: BLEManager = .init()
var centralManager: CBCentralManager?
var peripheralManager: CBPeripheralManager?
var scannedPeripherals: [CBPeripheral] = .init()
var confirmedPeripheralStates: [String: CBPeripheralState] = .init()
override init() {
super.init()
self.centralManager = .init(delegate: self, queue: nil)
self.peripheralManager = .init(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOn:
self.startScan()
default:
central.stopScan()
}
}
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
print("[BLE] state: \(peripheral.state.rawValue)")
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
self.scannedPeripherals.appendAsUnique(peripheral)
central.connect(peripheral)
self.confirm(peripheral)
}
func startScan() {
guard let central = self.centralManager else {
return
}
let isBackgroundMode: Bool = (UIApplication.shared.applicationState == .background)
let cbuuids: [CBUUID] = self.scannedPeripherals.map { CBUUID(nsuuid: $0.identifier) }
central.stopScan()
central.scanForPeripherals(withServices: isBackgroundMode ? cbuuids : nil)
}
func confirm(_ peripheral: CBPeripheral) {
let uuid: String = peripheral.identifier.uuidString
// Prevent duplicate logging.
if peripheral.state != self.confirmedPeripheralStates[uuid] {
self.log(peripheral)
}
self.confirmedPeripheralStates[uuid] = peripheral.state
}
func log(_ peripheral: CBPeripheral, completion: (() -> Void)? = nil) {
guard let name = peripheral.name,
let state = self.getPeripheralStateAsString(peripheral.state) else {
return
}
print("[BLE] \(name) was \(state) on \(self.getApplicationStateAsString())")
}
}
extension BLEManager {
func getApplicationStateAsString() -> String {
switch UIApplication.shared.applicationState {
case .active:
return "active"
case .inactive:
return "inactive"
case .background:
return "background"
}
}
func getPeripheralStateAsString(_ state: CBPeripheralState) -> String? {
switch state {
case .disconnected:
return "disconnected"
case .connecting:
return "connecting"
case .connected:
return "connected"
default:
return nil
}
}
}
extension Array where Element: Equatable {
mutating func appendAsUnique(_ element: Element) {
if let index = self.index(where: { $0 == element }) {
self.remove(at: index)
}
self.append(element)
}
}
AppDelegate.swift
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
BLEManager.shared.startScan()
}
Did I miss anything?
I need to check if Bluetooth is On.
I use this code:
func startDetectingBluetoothState() {
if self.centralManager == nil {
self.centralManager = CBCentralManager(delegate: self, queue: self.workingQueue, options: [CBCentralManagerOptionShowPowerAlertKey: false])
}
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
let state = central.state
DispatchQueue.main.async { [weak self] in
// notify observers about state change
self?.stateObservers.invokeDelegates { stateObserver in
stateObserver.bluetoothStateChanged()
}
}
}
I start the app on iPhone X 11.3 (Bluetooth is On in the Settings), then almost immidiatly it enters in centralManagerDidUpdateState(_ central: CBCentralManager) with state == .poweredOff. So the value is wrong. Then I turn off Bluetooth in Settings, centralManagerDidUpdateState(_ central: CBCentralManager) function is not called (because the previous state it detected was .poweredOff), then I turn on Bluetooth and centralManagerDidUpdateState(_ central: CBCentralManager) is called with .poweredOn value. Then it detects every state change properly. But if I restart the app and Bluetooth is On, it can't detect it again. If during start the Bluetooth is Off, everything is OK.
I have another device iPhone 6+ 11.2.5, where everything works as a charm :)
You need to implement CBCentralManagerDelegate. Below is the sample code which might help you to check the condition -
var centralManager:CBCentralManager!
in init() or viewDidLoad()
{
centralManager = CBCentralManager()
centralManager.delegate = self
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
print("Bluetooth is connected")
}
else if central.state == .poweredOff{
print("Bluetooth is not Connected.")
}
}
Use this Code Working for you
var centralManager:CBCentralManager!
override func viewDidLoad()
{
super.viewDidLoad()
// This will trigger centralManagerDidUpdateState
centralManager = CBCentralManager(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager)
{
print("Central Manager did update state")
if (central.state == .poweredOn)
{
self.centralManager?.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: false])
}
else
{
// Bluetooth is unavailable for some reason
// Give feedback
var message = String()
switch central.state
{
case .unsupported:
message = "Bluetooth is unsupported"
case .unknown:
message = "Bluetooth state is unkown"
case .unauthorized:
message = "Bluetooth is unauthorized"
case .poweredOff:
message = "Bluetooth is powered off"
default:
break
}
print(message)
UIAlertView(
title: "Bluetooth unavailable",
message: message,
delegate: nil,
cancelButtonTitle: "OK")
.show()
}
}
Running IOS 9.1 with Xcode 7.1.1 under 10.11.1. Cut'n'pasted this code from this tutorial; and double check it with several other sources/sites.
http://hatemfaheem.blogspot.ch/2014/12/how-would-you-scan-for-nearby-ble.html
This is the code I have ...
import Foundation
import CoreBluetooth
class BLEManager {
var centralManager : CBCentralManager!
var bleHandler : BLEHandler // delegate
init() {
self.bleHandler = BLEHandler()
self.centralManager = CBCentralManager(delegate: self.bleHandler, queue: nil)
}
}
class BLEHandler : NSObject, CBCentralManagerDelegate {
override init() {
super.init()
}
func centralManagerDidUpdateState(central: CBCentralManager) {
switch (central.state)
{
case .Unsupported:
print("BLE is unsupported")
case .Unauthorized:
print("BLE is unauthorized")
case .Unknown:
print("BLE is unknown")
case .Resetting:
print("BLE is reseting")
case .PoweredOff:
print("BLE is powered off")
case .PoweredOn:
print("BLE is powered on")
central.scanForPeripheralsWithServices(nil, options: nil)
default:
print("BLE default")
}
}
func centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) {
print("didConnectPeripheral")
}
func centralManager(central: CBCentralManager!,
didDiscoverPeripheral peripheral: CBPeripheral!,
adverismentData: [NSObject : AnyObject]!,
RSSI: NSNumber!)
{
print("\(peripheral.name) : \(RSSI) dBm")
}
}
Which I invoke in the View Controller with this code
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var bleManager: BLEManager!
bleManager = BLEManager()
}
Now I run it on an iPad Air with 9.1 and plug in and unplug and replug an ibeacon but nothing appears on the console, suggesting it simply isn't working. Now I know the ibeacon is working; cause I find it with the ScanBeacon tool by Radius Networks.
OK I understand that ibeacons and Core Bluetooth don't go together so well, but surely didDiscoverPeripheral should be called ? Or have I missed a critical line in my code?
Your BLEManager is going out of scope and being deallocated at the end of viewDidLoad. Make it a member variable to give it a longer, more useful lifetime:
var bleManager = BLEManager()
override func viewDidLoad() {
// etc
}