Category: iOS

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:

Using File Wrappers in a SwiftUI App

What Is a File Wrapper?

A file wrapper is a bundle, which is a collection of one or more directories (folders) and files that appears as a single file in the Finder (Mac) or Files app (iOS). Most Mac applications use bundles. If you want to see what a bundle looks like, select an application, right-click, and choose Show Package Contents.

When should you use a file wrapper? Use a file wrapper when you want to save your app’s data in multiple files. Suppose you’re developing a website building app. A website can have multiple pages, plus folders and files for images, videos, and custom CSS. By using a file wrapper you can save all these files and have it look like a single file to the person using the app. Most apps don’t need to use file wrappers

Types of File Wrappers

The FileWrapper class has the following file wrappers:

  • Directory
  • Regular file
  • Symbolic link

The symbolic link file wrapper points to a file. The most common case of using a symbolic link file wrapper is to point to a large file (image, audio, or video file) to keep the wrapper from getting too big. I’m going to focus on directory and regular file wrappers in this article.

Making Your File Wrapper Appear as a Single File

Document-based apps are more likely to use file wrappers than shoebox apps. If you forget to configure the document to be a file wrapper, the document will appear as a folder instead of a single file.

To configure the document to be a file wrapper, perform the following steps:

  1. Select the project from the project navigator to open the project editor.
  2. Select the app from the Targets list in the project editor.
  3. Click the Info button at the top of the project editor.
  4. Click the disclosure triangle next to the Exported Type Identifiers section in the project editor.
  5. Enter com.apple.package in the Conforms To text field.
  6. Click the disclosure triangle next to the Imported Type Identifiers section in the project editor.
  7. Enter com.apple.package in the Conforms To text field.

Creating a Document File Wrapper

You must create a file wrapper when saving the document. If you create a document-based SwiftUI app, you should see the following function in the document struct’s Swift file:

This function is called when saving the document. Your code to create the file wrapper goes in this function.

To create a file wrapper you must perform the following tasks:

  • Create a directory file wrapper
  • Convert your app’s data to a Data object
  • Create a regular file wrapper
  • Add the file to a directory file wrapper

Creating a Directory File Wrapper

At a minimum you must create a root directory for the file wrapper. You will return this wrapper in the call to fileWrapper. To create a directory file wrapper, call the FileWrapper method directoryWithFileWrappers and supply an empty Swift dictionary.

Call directoryWithFileWrappers and supply an empty dictionary for any additional directories you want to create.

The root directory of a wrapper does not need a name because it does not appear in the Finder or Files app. But any other directories you create require a name. Set the preferredFilename property to name the directory.

Call the addFileWrapper method to add the directory to the root directory.

Convert App Data to a Data Object

Files in file wrappers store their data in a Data object. You must convert your app’s data to Data to use file wrappers. Converting to Data depends on what you are storing, but the following code converts a string to Data:

Creating a Regular File Wrapper

To create a file wrapper for a regular file, call the FileWrapper method regularFileWithContents and supply the Data object that contains the file’s data.

Set the preferredFilename property to name the file.

In a real app you won’t be hardcoding filenames often. Suppose you have a document that has a list of pages. You would use the page’s title as the filename instead of giving the file a specific name. Remember that you use file wrappers to save multiple files. If you have 20 files to save, hardcoding the name of each file is going to be a pain. The following code demonstrates how to save a collection of text files:

Call addFileWrapper to add a file to a directory.

Reading from a File Wrapper

At this point you know how to create a file wrapper and save data to it. The next step is to read data from the file wrapper when opening a document.

The SwiftUI document structure provides the following initializer to read data from a file wrapper:

This initializer is where you add the code to read from the file wrapper. To read from a file wrapper, you must perform the following tasks:

  • Access the directory holding the files
  • Read the individual files
  • Load the data from the file

Access the Directory Holding the Files

If you store all the files inside the root directory, you won’t need to write any code to access the directory. But if you have files inside a subdirectory, you must call the fileWrappers method and supply the name of the subdirectory as the dictionary value.

The SwiftUI initializer takes a read configuration as an argument. The ReadConfiguration struct has a file property that provides access to the root directory of the file wrapper.

Reading the Individual Files

Use the fileWrappers property to access the files in a directory file wrapper. Go through each file and load the data. Use the regularFileContents property to access the file contents.

Loading the Data

The file wrapper’s regularFileContents property gives you a Data object to load the file’s contents in your app. Loading the file’s contents depends on the type of data being stored. The following code loads a file’s contents into a string variable:

Sample Project

I have a sample project on GitHub that demonstrates saving data to a file wrapper. Look at the files Wiki.swift and Page.swift.

Creating a Master-Detail Inteface in SwiftUI

Master-detail interfaces are common in iOS and Mac apps. Select an item from the master view to see additional information in the detail view. In SwiftUI you use two views to create a master-detail interface: NavigationView and NavigationLink.

A navigation view is the most common way to create a master-detail interface in SwiftUI. On iOS selecting an item from the master view, which is usually a SwiftUI list, takes you to the detail view. On macOS selecting an item from the master view shows the details in the detail view.

The following code shows how to create a navigation view:

In a real app you will pass data as arguments to the master view and detail view. The data depends on your app, which is why I didn’t supply any arguments in the code listing.

The final part of creating a master-detail interface is to add a navigation link in the master view and set its destination to the detail view.

This example creates a navigation link for each item in the list. Selecting an item from the list fills the detail view with information about the selected item. In a real app you will pass the data to show in the detail view as an argument to the detail view.

Handling Selection

There are some situations where you may need to hold on to the selection. Mac apps need access to the selected item when deleting an item from a list.

In the master view create a property to store the selection. Make it optional and set its value to nil.

When someone selects an item from the list, the selectedItem property will store it. In the call to create the navigation link, add the tag and selection arguments. The tag is usually the current item. The selection is the selected item.

Sample Project

I put a sample project on master-detail interfaces on GitHub. It builds upon the project from the Make a Markdown Editor in SwiftUI article by displaying a list of Markdown pages to edit. Look at the files ContentView.swift and PageListView.swift for the code on making the master-detail interface.

Resources for Learning Combine

Combine is Apple’s framework for handling asynchronous events in Swift. Combine and SwiftUI are the future for making iOS and Mac apps. This post lists resources for learning Combine.

Books

Websites

I don’t know of any websites focused solely on Combine, but the following websites have articles on Combine:

Saving Data When Your App Quits

A common scenario for iOS and Mac apps is to save data when someone quits the app. You have written a function to save the data. Where do you make the call when the app quits? This article answers that question, both for the SwiftUI app life cycle and the classic AppDelegate life cycle.

Using the SwiftUI App Life Cycle

In Xcode 12 Apple introduced a native SwiftUI app life cycle. The new life cycle has a scenePhase environment value that stores the app’s current phase, such as active, inactive, or background.

Add a property to the app struct for the scene phase. Add the .onChange modifier to the app’s window group or document group. Check if the phase is .background. If it is, make a call to save the data. The following code shows how to check when the app goes in the background:

Using the AppDelegate Life Cycle

If your Xcode project has a file named AppDelegate.swift, it is using the AppDelegate life cycle. Open the AppDelegate.swift function, and you should see multiple empty functions for handling app events.

An iOS app that wants to save when someone quits the app should make a call to save the data in the function applicationDidEnterBackground. This function is called when the person quits your app.

A Mac app should make the save call in applicationWillTerminate.

Where are the Single View and Master-Detail App Project Templates?

Prior to Xcode 12 Xcode had the following iOS application project templates:

  • Single View App
  • Master-Detail App
  • Page-Based App
  • Tabbed App

If you create an iOS application project in Xcode 12, you will notice these project templates are missing. In Xcode 12 Apple consolidated these project templates into one template: App. If you are following a tutorial that tells you to choose one of the older templates that no longer exist, choose the App template.

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:

Should I learn Swift or SwiftUI?

I see a lot of new iOS developers ask the question in the title of this article. The short answer is both. To write SwiftUI apps you must also learn Swift because Swift is the programming language SwiftUI uses. The rest of this article provides a more detailed explanation.

UIKit

In 2008 Apple gave developers a SDK (Software Development Kit) to let them make native iOS apps. The heart of the SDK was UIKit, a framework for creating the views and controls in iOS apps, such as buttons, text fields, and table views.

UIKit uses the Objective-C programming language. Using Objective-C worked well for existing Mac developers, who were already using Objective-C to make Mac apps. But the release of the iOS SDK attracted lots of new developers to the Apple developer community. These new developers found Objective-C’s syntax to look strange and found it difficult to understand. They wanted a language with a syntax that looked more familiar.

Swift

In 2014 Apple unveiled their solution for developers who hated Objective-C: the Swift programming language. Swift’s syntax looked more familiar to people who came to iOS development from web development and Windows development. Now iOS developers had two language choices for making iOS apps with UIKit: Swift and Objective-C.

SwiftUI

In 2019 Apple released the initial version of SwiftUI, a new framework for developing apps for all of Apple platforms: iOS, Mac, tvOS, and watchOS. SwiftUI has only one language: Swift. You must know Swift to use SwiftUI.

The Language and Framework Options for iOS Apps

Someone who wants to make iOS apps with Apple’s frameworks has three options.

  1. Use UIKit with Objective-C.
  2. Use UIKit with Swift.
  3. Use SwiftUI with Swift.

Which option should use choose? You won’t be making a mistake no matter which option you choose. But for someone new to iOS development, I recommend Option 3, write the app with SwiftUI using Swift. SwiftUI is the future of iOS development. You’re eventually going to have to learn it so you might as well learn it now.

Make a Markdown Editor in SwiftUI

In this tutorial you will make a Markdown editor with live preview using SwiftUI. This editor will run on both iOS and Mac. It’s a nice example to demonstrate how to create a document-based, multi-platform SwiftUI app. Plus, it shows how to use a WebKit web view in SwiftUI.

This tutorial uses the new document-based app features Apple added in Xcode 12. You must be running Xcode 12.2+ to create a multi-platform SwiftUI app. The iOS app requires iOS 14+, and the Mac app requires macOS 11+. If you are running macOS 10.15 (Catalina) on your Mac, you can still make the editor. You won’t be able to run the Mac version.

To keep the tutorial from getting too long, I will gloss over some steps and explanations that I covered in the following articles:

Create the Project

Start by creating the project.

  1. Choose File > New > Project in Xcode.
  2. Click the Multiplatform button at the top of the New Project Assistant.
  3. Choose Document App from the list of application templates.
  4. Click the Next button.
  5. Name the project in the Product Name text field.
  6. Click the Next button.
  7. Choose a location to save the project.
  8. Click the Create button.

If you look at the project navigator, you will see Xcode creates the following folders for your project:

  • The Shared folder contains files that both the iOS and Mac app targets use.
  • The iOS folder contains files that the iOS app uses.
  • The Mac folder contains files that the Mac app uses.

In the Shared folder are Swift files for the app, document, and content view.

Run the project and you will see that you have a working plain text editor. You can create documents, edit text, save documents, and load documents.

Add the Ink Package

This project uses the Ink Markdown parser to convert the Markdown you type in the text editor to HTML that you will preview. Ink has Swift Package Manager support to simplify adding it to your Xcode project.

Select the name of your project from the project navigator to open the project editor. Select the project from the project editor and click the Swift Packages button at the top of the editor to add the Ink package.

A current limitation in Xcode’s Swift Package Manager support is you can add a package to only one target initially. To add Ink to the other app target, select the target from the project editor and add the Ink framework from the Frameworks, Libraries, and Embedded Content section.

AddSwiftPackageToSecondTarget

Add Web Views

To preview the Markdown you write, you need a web view to display the HTML. SwiftUI does not currently have a native web view so you must use a WebKit web view, WKWebView, for the preview.

WKWebView has both iOS and Mac support, but you’re going to have to create two web view files: one for the iOS app and one for the Mac app. The code is going to be almost identical. The reason you need two files is that SwiftUI has separate protocols for working with UIKit(iOS) and AppKit(Mac) views.

Create one new Swift file in the iOS folder in the project navigator and create a second file in the Mac folder. Make sure the file in the iOS folder is a member of the iOS app target. Make sure the file in the Mac folder is a member of the Mac app target. You can set the target membership for a file using the File Inspector on the right side of the project window.

Add the following code to the iOS Swift file:

The web view uses the SwiftUI and WebKit frameworks so you must import them. When you use a UIKit view in a SwiftUI app, the view must conform to the UIViewRepresentable protocol. The html property stores the HTML to display in the web view.

There are three functions. The init function initializes the web view with some HTML to display. The makeUIView function creates the web view. The updateUIView function loads the HTML string to show in the web view.

The Mac code is almost identical. Replace anything starting with UI with NS.

Parsing the Markdown

The next step is to write some code to parse the Markdown to HTML. Open the ContentView.swift file. Import the Ink framework.

Add the following computed property to the ContentView struct:

This code creates an Ink parser and calls a function that converts the document text into HTML. This code is much easier to write than creating your own Markdown parser.

Build the User Interface

The last step is to build the user interface. The user interface contains a text editor and a web view side by side.

The body property for the content view should look like the following code:

The code creates a horizontal stack so the text editor and web view are side by side. The text editor is on the left and contains the document’s text. The web view is on the right and displays the HTML you parsed from the Markdown text. The iOS app uses the iOS version of the web view. The Mac app uses the Mac version of the web view.

Run the project. Any Markdown you type in the text editor will appear as HTML in the web view. The Ink parser does not currently support creating code blocks with indentation. You must add three backticks on the line above and below the code to create a code block.

I have the project on GitHub for you to look at if you run into problems.

Saving Data in a Swift App

People new to iOS development frequently ask how to save data in their apps. Apple provides the following technologies for saving app data on a local device:

  • UserDefaults
  • Keychain
  • Files
  • Core Data

You can use multiple methods to save data in your app. Many iOS and Mac apps use both UserDefaults and Core Data to save data. Use all the methods for saving data that make sense for your app.

UserDefaults

Apple provides a UserDefaults class that provides a programming interface to your app’s defaults database. The defaults database contains user settings and preferences.

Use UserDefaults to store small amounts of data, like settings and preferences. If you are writing a plain text editor, you could store the font to use in UserDefaults.

Keychain

Use the keychain to store small amounts of data securely, such as passwords. If you were writing a Twitter app, you could use the keychain to store the person’s Twitter handle and password.

Apple provides a Keychain Services API for apps to work with the keychain. The documentation is in the System > Security section of Apple’s documentation in Xcode. Choose Help > Developer Documentation in Xcode to read the documentation.

Files

You can use files to store larger amounts of data. Document-based apps, such as text editors, spreadsheets, and drawing apps, usually save their data in files.

Apple provides the Codable protocol to simplify reading and writing data to files. An Internet search for Swift Codable tutorial yields many results, most of which cover JSON.

Core Data

Core Data is Apple’s framework for creating and working with a data model for iOS and Mac apps. One of the things Core Data does is save data for you.

Core Data is a huge subject. People have written books about it. You can find an overview of Core Data in Apple’s documentation in Xcode under App Services. Apple also has a Core Data Programming Guide on their developer website.

Core Data or Files?

Should you use Core Data or files to save larger amounts of data?

Core Data is a powerful framework that does a lot, much more than saving app data. But with that power comes a learning curve.

You must determine if your app’s data needs require Core Data. Avoid Core Data (or at least do some more research before using Core Data) in the following cases:

  • Your app supports non-Apple platforms like Android, Linux, and Windows. Core Data does not run on non-Apple platforms.
  • Your data can easily be saved in one file.
  • Your app data has only one class or struct.
  • You are writing a document-based app. Core Data supports document-based apps, but it doesn’t add much to Apple’s document architecture.

A text editor is an example of a project where you should save your data in a file. The app data is just a bunch of text. The data can easily be saved in one file. Core Data would not help you much.

Use Core Data to save your app’s data in the following cases:

  • You store large amounts of data in the app.
  • The data makes more sense to store in a database than in a single file.
  • Your app data has multiple classes or structs.
  • You are writing a shoebox (not document-based) app.

A blog editor is an example of a project where you should use Core Data. A blog can contain hundreds of posts that would be difficult to store in a file. A blog has multiple pieces of data: titles, tags, categories, and authors. Core Data can do things like show you all the blog posts with a given tag. Take advantage of Core Data if it will help for your app.

If you are still not sure whether or not to use Core Data, use it.