Executing function for lower iOS versions - swift

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")
}

Related

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"

"Result of '&&' unused" warning in a do/try/catch block

I've been trying to figure out what is going on to no avail. I've distilled the code as much as possible but I still get the "Result of operator && is unused warning (even though it is used) if I do it in a project but the same code copied to Playground is working fine with no warnings. This is just some dummy code, after me rewriting the basic code again while trying to find the problem.
enum WordError: Error {
case tooShort
case tooLong
}
func isTooShort(_ word: String) throws -> Bool {
if word.count < 3 { throw WordError.tooShort }
return true }
func isTooLong(_ word: String) throws -> Bool {
if word.count > 5 { throw WordError.tooLong }
return true }
func check(_ word: String) {
do {
try isTooShort(word) && isTooLong(word)
print(word)
} catch let error as WordError {
print("\(error)")
} catch {
}
}
Is this just a bug or am I doing something wrong here?
I figured I can silence the warning if I use:
try _ = isTooShort(word) && isTooLong(word)
But I'm not sure whether that's the right way of 'patching' it.
There is nothing wrong with doing it that way. The "right" way, when something like isTooShort belongs to you and you want to call it without capturing the result, is to mark it with #discardableResult. If you did that, then you could write
do {
try isTooShort(word)
try isTooLong(word)
print(word) // if we get here, it's neither too short nor too long
} catch ...
But what you're doing is also "right" in these circumstances.
The real question is why you would both return a Bool and throw an error. Your implementation of isTooShort is very odd. You seem to be misusing throw. It isn't clear what problem you are trying to solve by implementing it in this odd way. isTooShort can only fail one way: the word is too short. So why doesn't it just return a Bool? isTooShort asks a simple yes/no question, so just answer it: return a Bool and stop.
If your goal is to answer a three-way question - i.e., to tell the caller whether this word was too short, too long, or just right, then again, just return a custom enum that answers the question:
enum WordLength {
case tooShort
case tooLong
case justRight
}
func howIs(_ word: String) -> WordLength {
if word.count < 3 { return .tooShort }
if word.count > 5 { return .tooLong }
return .justRight
}

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

swift 2.3 how to properly use deprecated CBCentralManagerState

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
}