CBCentralManagerState deprecated iOS 10

Hello, we use CBCentralManagerState in our bluetooth app's communication:


            print("central.state: \(central.state)")
            self.centralManagerState = central.state as! CBCentralManagerState
            switch self.centralManagerState {
            case CBCentralManagerState.unauthorized:
                break
            case CBCentralManagerState.poweredOff:
                break
            case CBCentralManagerState.poweredOn:
                if queueScanning {
                    queueScanning = false
                    print("update state startScanning")
                    self.startScanning()
                }
                break
            case CBCentralManagerState.resetting:
                break
            default:
                /
                break
            }


In iOS 10 it is deprecated and wea re to use CBManagerState and we can wrap it in the if #available(iOS 10.0, *) {} else {} as below

    public func centralManagerDidUpdateState(_ central: CBCentralManager) {
        if #available(iOS 10.0, *) {
            self.managerState = central.state
            switch central.state {
            case CBManagerState.unauthorized:
                break
            case CBManagerState.poweredOff:
                break
            case CBManagerState.resetting:
                break
            case CBManagerState.poweredOn:
                if queueScanning {
                    queueScanning = false
                    print("update state startScanning")
                    self.startScanning()
                }
                break
            
            default:
                break
            }
        } else {
            print("central.state: \(central.state)")
            self.centralManagerState = central.state as! CBCentralManagerState
            switch self.centralManagerState {
            case CBCentralManagerState.unauthorized:
                break
            case CBCentralManagerState.poweredOff:
                break
            case CBCentralManagerState.poweredOn:
                if queueScanning {
                    queueScanning = false
                    print("update state startScanning")
                    self.startScanning()
                }
                break
            case CBCentralManagerState.resetting:
                break
            default:
                /
                break
            }
        }


However a couple of errors occur.


1. The else block will not compile as it also wants us not to use CBCentralManagerState

2. the declaration of var managerState: CBManagerState wants us to @available(iOS 10.0, *) on the whole class, which then filters errors throught the entire application that uses this class.


Any suggestions on how to support iOS 9 and iOS 10 with swift 3.0 would be appreciated. At this time we sould not be able to move to Swift 3 if this cannot be resolved as we have to support 9.x as well as 10.x


Thank you

Replies

I described a solution here: https://forums.developer.apple.com/message/143024#147093


Just replace all your CBCentralManagerState and CBPeripheralManagerState enums by CBManagerState.

The enums are binary compatible so your code will run fine on any iOS version.

If you want to still be able to compile your code with Xcode 7, you can add some really simple defines.


#if __IPHONE_OS_VERSION_MAX_ALLOWED <= __IPHONE_9_3 
#define CBManagerState CBCentralManagerState 
#define CBManagerStateUnknown CBCentralManagerStateUnknown 
#define CBManagerStateResetting CBCentralManagerStateResetting 
#define CBManagerStateUnsupported CBCentralManagerStateUnsupported 
#define CBManagerStateUnauthorized CBCentralManagerStateUnauthorized 
#define CBManagerStatePoweredOff CBCentralManagerStatePoweredOff 
#define CBManagerStatePoweredOn CBCentralManagerStatePoweredOn 
#endif

I'm stuck exaclty with this problem. Did you manage to solve it?

Even with the answer I'm not able to fix it as I'd like to have an application which supports iOS9 and iOS 10, which seems cannot be done?

Regards,

I found an easy solution. For me it's a huge bug in the swift implementation of the corebluetooth in ios10. What you have to avoid by any means is to use explicilty the CBManagerState enum.

To solve it, what I did is to define an extension for CBCentralManager in your class which add a new member variable of type CBCentralManagerState.

Then in your class use this new varialbe instead of the old state.

Could you post the code of your fix? It seems pretty neat.

Thanks.

I worked around this issue on Xcode 8 with Swift 2.3 (targeting iOS 8 and above) with ttboxer's suggestion above. I created an extension property on `CBCentralManager` which is of the old enum type, `CBCentralManagerState`. I named it `centralManagerState`. I refer to `CBCentralManager.centralManagerState` where I used to refer to `CBCentralManager.state`.



extension CBCentralManager {
     internal var centralManagerState: CBCentralManagerState  {
          get {
               return CBCentralManagerState(rawValue: state.rawValue) ?? .Unknown
          }
     }
}


I posted the same answer on Stack Overflow.

Thank you, this exactly what I needed.


Is the key to all this the fact that anyone targetting below iOS 10 can access state via the raw value, but not the enum type that state actually is because of the iOS 10 requirement?


I don't want to copy and paste code without really understanding it, but it certainly seem like Apple has forced our hand here with this catch 22.

I will explain how I understand this issue. Others can correct me where I'm wrong...


The CBCentralManager class and the CBManagerState, CBCentralManagerState enums are implemented in Objective C. In Objective C enums are thinly veiled integer constants. Since CBManagerState and CBCentralManagerState are defined with the same textual names in the same order, they translate to the same set of Integer values. Therefore, at some level they are interchangeable.


That is why this code is valid (where centralManager.state is of type CBManagerState)


let centralManager = CBCentralManager()
let cbCentralManagerState = CBCentralManagerState(rawValue: centralManager.state.rawValue)

The Swift compiler sees CBManagerState and CBCentralManagerState as different types which are not interchangeable regardless of their underlying similarities. That is why the Swift compiler warns that the following "Cast from 'CBManagerState' to unrelated type 'CBCentralManagerState' always fails".

let centralManager = CBCentralManager()
let cbCentralManagerState = centralManager.state as? CBCentralManagerState

The Swift compiler in the first production release of Xcode 8 does not allow a reference to the enum "CBManagerState" in your code if you are targeting an iOS version below iOS 10 even though Xcode shows CBCentralManager's state property is of type CBManagerState.

Example 1

The following code will not compile because " 'CBManagerState' is only available on iOS 10.0 or newer"


let centralManager = CBCentralManager()
let cbManagerState: CBManagerState = centralManager.state


The equivalent code does compile since it does not reference the enum type CBManagerState explicitly. Instead, the type is inferred.


let centralManager = CBCentralManager()
let cbManagerState = centralManager.state


Example 2

The following code will not compile because " 'CBManagerState' is only available on iOS 10.0 or newer"


  switch centralManager.state {
  case CBManagerState.unauthorized:
       break
  case CBManagerState.poweredOff
       break
  case CBManagerState.poweredOn:
       break
  case CBManagerState.resetting:
       break
  default:
       break
  }


However, the equivalent code does compile since it does not reference the enum type CBManagerState explicitly.


  switch centralManager.state {
  case .unauthorized:
       break
  case .poweredOff
       break
  case .poweredOn:
       break
  case .resetting:
       break
  default:
       break
  }


If you want to create a variable of type CBManagerState, it is possible so long as the type is inferred like this.


  var cbManagerState = centralManager.state
  cbManagerState = .poweredOff


In the example above, I can even change the variable's value to CBManagerState.poweredOff because CBManagerState is inferred.


I suspect that the original code in the top of this thread could be made to compile by removing explicit references to CBManagerState.