swift 2.3 how to properly use deprecated CBCentralManagerState - ios10

I'm just converting my project to swift 2.3 (XCode 8 beta 6) and I can't figure out how to use enum CBManagerState on old iOS versions (my app has deployment target iOS7).
CBCentralManager state now uses different enum CBManagerState (it was CBCentralManagerState before).
Code below does not compile because manager.state can't be compared with deprecated enum CBCentralManagerState so what should I put into else block?
Thanks for any advise!
func isBluetoothAvailable() -> Bool {
if #available(iOS 10.0, *) {
return manager.state == CBManagerState.PoweredOn
} else {
return manager.state == CBCentralManagerState.PoweredOn
}
}

I don't know whether this is a solid solution but removing the enum type seems to work...
func isBluetoothAvailable() -> Bool {
return manager.state == .PoweredOn
}

Related

Executing function for lower iOS versions

Is there an way for executing functions only for lower iOS versions.
I mean we can execute always using the available attribute for iOS versions greater than the specific one ; but is there any way to do the other way round?
It's simple. You can use this:
if #available(iOS 10, *) {} else {
print("Code only for versions below iOS 10")
}
It's really annoying to write if statement and then catch what you want in the else statement. Nevertheless you can define global function to handle all cases in one place, which is also have its own issues. Never call it with a variable! Always use hardcoded string version number to check iOS version. If you accidentally checked unhandled version, just insert it and go. fatalError() will help you.
func available(version: String) -> Bool {
switch version {
case "13.5":
if #available(iOS 13.5, *) { return true }
case "12":
if #available(iOS 12, *) { return true }
default:
fatalError("iOS \(version) is not handled. Insert:\n case \"\(version)\": \n if #available(iOS \(version), *) { return true }")
}
return false }
Usage:
if !available(version: "10") {
print("Not available")
}

Testing codepaths for older OS versions

If I have some library with methods like:
public struct Foo {
#available(macOS 10.15, *)
func greatNewFeature() -> String {
return "new feature"
}
func legacyFeature() -> String {
return "legacy feature"
}
}
Then some code that uses it:
func methodToTest() -> String {
let foo = Foo()
guard #available(macOS 10.15, *) else {
return foo.legacyFeature()
}
return foo.greatNewFeature()
}
Is there a way I can write unit tests which give me complete coverage of methodToTest?
All ideas I have had so far, have not been helpful:
you can't treat the availability check as injected functionality - the compiler specifically needs the #available keyword in order to use the greatNewFeature method.
you can't add some hacky boolean which you could set in the tests like #available(macOS 10.15, *) || isMacOS10_15 for a similar reason to the previous point.
The only thing I think would work is to run the test suite multiple times - once for each supported OS version, then create a script to combine the code coverage stats. Can anyone think of a better approach?
You can create a flag in your tests whether to skip the OS version check or not and && that with your #available check.
This way you simply need to call testFooFeature with the flag both turned on and off and you'll be able to test both code paths on macOS 10.15.
var forceOldOSVersion = true
func testFooFeature() -> String {
let foo = Foo()
if !forceOldOSVersion, #available(macOS 10.15, *) {
return foo.greatNewFeature()
} else {
return foo.legacyFeature()
}
}
testFooFeature() // "legacy feature"

Swift generic sequence observable ambiguity

I've got following code
protocol NamedOption {
var optionTitle: String { get }
}
struct DebugOption: NamedOption {
let optionTitle: String
let debugViewControllerType = UIViewController.self
}
func testFunk<T: Sequence>(d: Observable<T>) where T.Element == NamedOption {
}
func bindFullResultsRx() {
let dd: Observable<[DebugOption]> = self.dataModel.debugOptions // this is defined and properly assigned earlier
testFunk(d: dd)
}
and at the line where I call testFunk Xcode gives me following error:
Expression type '()' is ambiguous without more context
I have no idea why this is happening :( So far I was able to make it working by changing constraints on testFunk into this:
func funk<T: NamedOption>(d: Observable<[T]>) {
}
which seems to me more restrictive then version at the top. Does any one knows how to make it working with T: Sequence ?
Xcode version is 9.4, Swift version is 4.1.
After some digging and help from work colleagues I was able to make it working by simply changing == into : so it looks like this
func testFunk<T: Sequence>(d: Observable<T>) where T.Element: NamedOption {
}
It's just a matter of swift syntax
https://docs.swift.org/swift-book/ReferenceManual/GenericParametersAndArguments.html
conformance-requirement → type-identifier : protocol-composition-type
OO and generics don't play too well together. In order to do what you want, you have to manually cast up as in:
testFunk(d: dd.map { $0.map { $0 as NamedOption } })

Xcode 8.0 CBCentralManager Issue

I recently downloaded Xcode 8.0 and trying to run my previous project which uses core bluetooth.
I have enabled Use Legacy Swift Language Version in build setting for compatibility in swift 2.3
everything works, but one Issue occured,
func centralManagerDidUpdateState(central: CBCentralManager)
{
print("state is \(central.state.rawValue)")
if (central.state == CBCentralManagerState.PoweredOn)
{
self.centralManager?.scanForPeripheralsWithServices([serviceUUID], options: nil)
}
else
{
// do something like alert the user that ble is not on
}
}
previously central.state would return CBCentralManagerState type as int but now it returns CBManagerState so got an error so i changed to
if (central.state == CBManagerState.PoweredOn)
But CBManagerState is only supported in IOS 10+ but i want to build it for IOS 8.3+ so how can I change the code?
UPDATE
I also converted project to swift 3.0, but still same issue, so how can i run this project on mobiles with ios version below 10?
The simplest approach is just to use the short-hand reference to the enumeration value:
func centralManagerDidUpdateState(central: CBCentralManager)
{
print("state is \(central.state.rawValue)")
if (central.state == .PoweredOn)
{
self.centralManager?.scanForPeripheralsWithServices([serviceUUID], options: nil)
}
else
{
// do something like alert the user that ble is not on
}
}
Now your code will compile without errors or warnings and works correctly on all targets

Compare to nil and return a bool in Swift

UPDATE: This question is no longer a problem after Xcode beta5
Seems the beta3 release refactored how "nil" is working under the hood but didn't come with enough documentation.
I have this piece of code works fine in beta2:
func hasLogin() -> Bool {
return self.credentail != nil
}
But in beta3, I got this error
Type 'NativeObject' does not conform to protocol 'NilLiteralConvertible'
self.credential is an optional value of 'Credential' protocol and implemented by NSObject subclass
#objc
protocol Credential: NSObjectProtocol, NSCoding {
}
var credentail: Credential?
I could make it work by doing doubled negation like this, but it really looks ABSURD
func hasLogin() -> Bool {
return !(!self.credentail)
}
So is this a bug in Swift or I'm doing something wrong?
The under-the-covers method that Swift uses to turn non-Bool values in booleans for use in if statements is the getLogicValue() method of the LogicValue protocol (which Optional implements):
func hasLogin() -> Bool {
return self.credential.getLogicValue()
}
This was working for me:
func hasLogin() -> Bool {
return self.credential ? true : false
}
The release notes of XCode 6 Beta 5 state the following:
Optionals no longer conform to the BooleanType (formerly LogicValue)
protocol, so they may no longer be used in place of boolean
expressions (they must be explicitly compared with v != nil). This
resolves confusion around Bool? and related types, makes code more
explicit about what test is expected, and is more consistent with the
rest of the language.
Note that ImplicitlyUnwrappedOptional still includes some BooleanType
functionality. This !issue will be resolved in a future beta.
(17110911)!
This means your previous approach should work again:
func hasLogin() -> Bool {
return self.credentail != nil
}