Category: Swift Language

Save Data in Your Swift App with PropertyListEncoder

Apple provides a PropertyListEncoder class to save data as a dictionary in a property list file. If your app’s data can be saved to a dictionary, using PropertyListEncoder provides a relatively easy way to save the data.

You must do the following to save app data with PropertyListEncoder:

  • Create a struct or class that conforms to the Codable protocol.
  • Create a CodingKeys enum for the properties in your struct or class.
  • Create a property list encoder and encode the data.

Creating the Struct or Class

For this article, I’m going to use a stripped down version of a struct I created in Phel to store an Apple help book’s Info.plist file. The following block shows the code for the struct:

Every property in the struct or class must also conform to Codable. If you can’t get the struct’s properties to conform to Codable, you must use the NSKeyedArchiver class to create the property list. The following article shows you how to use NSKeyedArchiver:

Saving with NSKeyedArchiver

Creating a CodingKeys Enum

It’s not mandatory to create a CodingKeys enum, but if you don’t create the enum, the property list encoder uses the name of the property as the key. In many cases the key must have a specific value. The CodingKeys enum lets you save the property list with the correct key names while letting you use clear property names in your code.

Add the CodingKeys enum inside your struct or class. Add a case for each property. The following code shows the enum for the HelpBookInfo struct.

When saving the property list, the file will use the key CFBundleName to store the name of the app, the key HPDBookTitle to store the name of the help book, and the key CFBundleIdentifier to store the help book’s bundle identifier.

Creating the Property List Encoder

Now you can create the property list encoder to save data. Start by creating an instance of PropertyListEncoder.

Property lists have two possible formats: XML and binary. Set the encoder’s outputFormat property to set the format.

Call the encode method to save the data. Supply the value that you want to encode. The encode method can throw errors so you must wrap the call in a do-try-catch block.

Adding the following method to the HelpBookInfo struct saves the help book information to an XML property list:

Fixing the ForEach requires that T conform to Identifiable error in Swift

The actual error message is too long for an article title. The error message looks like the following:

Error: Referencing initializer ‘init (_:content:)’ on ‘ForEach’ requires that ‘T’ conform to ’Identifiable’

Where T is a struct or class.

Why am I getting this error?

You have a ForEach block that does not uniquely identify each item. The compiler needs a way to uniquely identify each item in the ForEach block. If you do not provide a way to uniquely identify the items, you get the error.

The most common cause of the error is the only argument you supplied to the ForEach is the collection of items, and the item’s struct or class does not conform to the Identifiable protocol.

Ways to fix the error

The easiest way to fix the error is to provide an id argument to the ForEach and using \.self as the value.

Another way to fix the error is to make the struct or class of the items in the ForEach conform to the Identifiable protocol. Add an id property to the struct or class that uniquely identifies each instance of the struct or class

The call to UUID creates a unique identifier for each instance of the struct your app creates.

Fixing the “Async call in a function that doesn’t support concurrency” error in Swift

The error message may also say “Cannot pass function of type ‘() async -> Void’ to parameter expecting synchronous function type”.

Why am I getting this error?

You are using Swift’s async await syntax and calling an async function in a function that does not support async await. If you have the following function that calls an async function:

You are going to get the “Async call in a function that doesn’t support concurrency” build error because myFunction is not an async function.

Apple added async await support to Swift in 2021 so the support is relatively new. Many of Apple’s frameworks do not support the new async await syntax.

Apple’s ASWebAuthenticationSession class, which apps use to log into websites, does not support async await. When you create an instance of ASWebAuthenticationSession, you supply a completion handler that runs after a successful login. If you make an async function call in the completion handler, you’ll get the “Async call in a function that doesn’t support concurrency” build error.

Ways to fix the error

The most common way to fix the error is to place the call to the async function in a Task block.

If the error occurs in a function you wrote, you can fix the error by marking that function as async.

Don’t forget to add await when calling the function you marked as async.

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.
  • Use the ?? operator to provide a default value in case the optional value is nil.

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.