Category: Swift Language

Using New API Features in Swift While Supporting Older OS Versions

Every year at WWDC Apple adds new features to their developer SDKs. Usually these new features require the upcoming version of iOS and macOS. You would like to use the new features while also supporting older versions of iOS and macOS. How do you do this?

Apple provides two Swift keywords to use both new code and old code in your apps: #available and @available.

#available

The #available keyword works with if and guard statements. Supply an operating system version version, and the code inside the if block (or after the guard) executes only on machines capable of running the code. Use the else block for code to run on older operating systems. The following example shows how to run one block of code on iOS 15 and another block on earlier iOS versions:

The following example shows how to run one block of code on macOS 12 and another block on earlier macOS versions:

What Does the Asterisk Do?

The asterisk in the last argument to #available tells the compiler to require the minimum deployment target for other platforms. You must always supply the asterisk to support future platforms Apple may add.

Checking for Multiple Platforms

Suppose you have a multiplatform SwiftUI project and you want to conditionally run code for iOS 15 and macOS 12. Supply the second platform operating system as an additional argument to #available. The following example checks for both iOS 15 and macOS 12:

guard Statements

Using available with a guard statement works best with code that should only run on newer versions of iOS and macOS. You can exit and do nothing on machines running on older operating systems.

@available

The @available keyword lets you mark a function, struct, or class as being available only on certain versions of iOS and macOS. Supply the same information as #available: an operating system version and an asterisk. The following code demonstrates the use of @available in a function:

Struct example:

Class example with multiple platforms:

Convert a Swift Range to Use String.Index

The Situation

I was working on a SwiftUI app where I wanted to select text in a text view and insert some text at the start of the selection. SwiftUI’s native text editor currently does not support getting the selection range so I had to wrap an AppKit text view and store the selection range when the selection changes.

The Problem

The code in the previous example gives me a Swift range, which uses integers, Range<Int>. I wanted to use the selection range’s start index as the destination to insert the string.

But the compiler gave me an error. The reason for the error is the String struct’s insert method takes a range that uses string indexes, Range<String.Index>, not integers.

The Solution

The solution is to create a string index for the string and use that as the argument to the insert method. The base for the string index is the string’s start index. The offset for the string index is the selection range’s lower bound.

Using Environment Variables in Swift Apps

When to Use Environment Variables

Most Swift apps do not need environment variables. A common reason to use environment variables is for apps that call a website’s REST APIs. To gain access to someone’s account on the website, your Swift app uses a client ID and client secret from the website. Storing the client ID and client secret in environment variables is safer than storing them as strings in your code.

Suppose you are developing a git client app. Your app wants to push commits to GitHub. To push commits to GitHub you must create an OAuth app for your app in GitHub. The GitHub OAuth app contains a client ID and a client secret that your Swift app uses to authenticate the user so your Swift app can access the person’s GitHub account. GitHub recommends using environment variables to store the client ID and client secret.

Add the Environment Variables from Xcode’s Scheme Editor

The first step to using environment variables is to add them to your Xcode project. Use Xcode’s scheme editor to add environment variables to your app. Use the jump bar next to the Run and Stop buttons in the project window toolbar to open the scheme editor.

XcodeSetEnvironmentVariablesBlurred

Select the Run step on the left side of the scheme editor. Click the Arguments button at the top of the scheme editor to access the environment variables. Use the Add (+) button to add the environment variables.

Accessing Environment Variables in Your Swift Code

After adding environment variables, you can access them in your code. Use the ProcessInfo class to access environment variables. The class has a processInfo property that contains a dictionary of values. The name of the environment variable is a key in the dictionary. Use that key to get the environment variable’s value. The following code demonstrates how to access a variable containing a GitHub OAuth app’s client ID:

Swift Optionals

I saw in a talk by Paul Hudson at NSSpain 2018 that optionals are what people learning Swift struggle with the most. That makes optionals a good topic to cover on a site called Swift Dev Journal.

What Is an Optional?

An optional is a data type for a variable where the variable either exists or doesn’t exist, in which case it’s nil. Pretty much anything in Swift can be an optional, including integers, strings, views, structs, and classes. Add ? to a variable’s type to make that variable an optional.

In this example if there’s a description, the description variable contains a string with the description. If there’s no description, description is nil.

Implicitly Unwrapped Optionals

A special kind of optional is an implicitly unwrapped optional. An implicitly unwrapped optional assumes the value of the optional is not nil. If the value is nil and you access that value in your app, the app crashes. Add ! to a variable’s type to make that variable an implicitly unwrapped optional.

Outlets are the most common use of implicitly unwrapped optionals. You can assume your outlets are not nil after loading a storyboard or xib file. If you forget to connect the outlets, your app will crash, but in that case, you want the app to crash so you can connect the outlets.

Outside of outlets you should avoid using implicitly unwrapped optionals because they’re not safe. Every implicitly unwrapped optional in your code is a potential crash.

Force Unwrapping

Force unwrapping an optional uses the value without checking if it’s nil. Add ! to an optional variable to force unwrap it.

Don’t force unwrap implicitly unwrapped optionals. They’re already unwrapped. If you add ! to an implicitly unwrapped optional, you’ll get a compiler error.

Force unwrapping is dangerous. In this example if description is nil, the app will crash.

You should avoid force unwrapping optionals because force unwrapping isn’t safe. Every optional you force unwrap is a potential crash.

Safely Unwrapping with if-let and guard

When writing Swift code, you often end up checking if an optional value is not nil, then assigning a constant with something from the optional value. Suppose you wanted to get a view controller’s parent (which is an optional because a view controller may not have a parent) and do something with it, you could write code like the following:

But this code is a bit tedious to write. Swift provides the if-let statement to combine the nil check and the assignment into one statement.

If parent is nil, the code inside the if block won’t execute.

Another way to safely unwrap an optional is to use a guard statement. The guard statement lets you exit quickly if the optional value is nil.

Crashing with Swift Optionals

If you have done any iOS or Mac programming in Swift, you have probably had your app crash with the following message in Xcode’s debugger:

If you don’t understand this error message, keep reading. In this article you’ll learn what this error message means, common causes of the message, and ways to fix your code so your app stops crashing.

What Does the Message Mean?

Before I can tell you what the error message means, I need to explain Swift optionals. An optional is a data type for a variable where the variable either exists or doesn’t exist, in which case it’s nil. Pretty much anything in Swift can be an optional, including integers, strings, arrays, table views, structs, and classes. The following code shows an example of declaring an optional variable:

A special kind of optional is an implicitly unwrapped optional. While an optional can either exist or not exist, an implicitly unwrapped optional must exist. If an implicitly unwrapped optional is nil and you attempt to use it, the app crashes. The following code shows an example of using an implicitly unwrapped optional:

If description exists, the code will print the description, but if description is nil, the code crashes.

Now to answer the question in the section heading. The error message Fatal error: Unexpectedly found nil while unwrapping an Optional value is saying the app is implicitly unwrapping a nil optional value. The error is similar to accessing a nil pointer in C, C++, or Objective-C.

What Causes the Error?

The general cause of the error is having an implicitly unwrapped optional that’s nil. But you’re looking for specific causes. I can think of three common sources of implicitly unwrapped optionals that are nil.

The first common source is forgetting to connect the outlets for your user interface elements. Outlets in Interface Builder are usually declared as implicitly unwrapped optionals because you can assume the outlet is going to exist after loading the storyboard or xib file. But if you forget to connect the outlet, the outlet is nil and your app will crash when it tries to use the outlet. The following screenshot shows what a disconnected outlet looks like in Xcode:

DisconnectedOutlet

The disconnected outlet is the circle above Line 14. A connected outlet has a filled-in circle.

The second common source is using as! to downcast to a specific type. Look at the following code to instantiate a view controller from a storyboard:

This code will crash in any of the following situations:

  • I forgot to give the view controller an identifier in the storyboard.
  • The identifier in the storyboard doesn’t match the string in the code.
  • I forgot to set the class of the view controller in the storyboard.

With implicitly unwrapped optionals it doesn’t take much to cause a crash.

The last common source is using implicitly unwrapped optionals in your code. If you see an exclamation point at the end of any of your variable names, you have a potential crash in your code. What doesn’t help matters is when you have a compiler syntax error in your code involving optionals, Xcode’s suggested fix is to make an implicitly unwrapped optional. Following Xcode’s advice makes your code more likely to crash.

How Do You Fix the Error?

The general fix to avoid these crashes is to avoid using implicitly unwrapped optionals. The following techniques will help you avoid crashes caused by implicitly unwrapped optionals:

  • Connect your user interface elements to the outlets in your code.
  • Use as? instead of as! when downcasting.
  • Use if let or guard statements to safely unwrap your optionals.

To connect your user interface elements to your outlets, open Xcode’s assistant editor so your source code file and your storyboard or xib file are both open. Select the user interface element in the storyboard or xib file. Hold the Control key down and drag to the outlet in your code to make the connection.

I can demonstrate the last two techniques by fixing the earlier example of instantiating a view controller from the storyboard. Using an if let statement and as? to downcast is enough to fix the code. The following code makes sure the view controller has been instantiated, then presents the view controller:

Now if I forgot to give the view controller an identifier or misspelled the name of the identifier, the app won’t crash. The app won’t present the view controller, but at least there won’t be a crash.