Category: Mac

Removing Items from SwiftUI Lists in Mac Apps

Most examples of removing items from SwiftUI lists use the .onDelete handler, which is not available for Mac apps. In this article I share what I learned to remove list items from SwiftUI Mac apps.

To remove items from SwiftUI lists in Mac apps, you must perform the following tasks:

  • Add a variable to the list view to store the selected list item.
  • If you are using a navigation link, supply a tag and selection when creating the link.
  • Make your struct or class conform to the Equatable and Hashable protocols.
  • Add the .onDeleteCommand handler to the list. The .onDeleteCommand handler is the handler SwiftUI Mac apps use to remove list items.

Add a Selection Variable to the List View

To remove an item from a SwiftUI list, the list view requires a variable to store the item you want to remove. Create an optional for the variable and set it to nil initially.

ListItemStruct is the name of the data structure in your app that you want to show in the list.

When you supply this selection when creating a navigation link, SwiftUI keeps track of the selected item in the list.

Most SwiftUI apps that use lists use a navigation link to create master-detail interfaces. Select an item from the list to show additional information in the detail view. Add a call to NavigationLink in the master view and set its destination to the detail view.

To support more complex selection behavior, you must supply two additional arguments to the navigation link call: tag and selection. Usually the tag is the current list item you’re adding to the list. The selection is the variable you added to the list view.

The following code demonstrates how to show a list of a book’s chapters:

This example uses the improved syntax Apple added in Xcode 13 to bind list text fields to items in an array. If you’re using Xcode 12 you will have to use a Text to display the titles and remove the $ character from $chapter.

Make Your Struct/Class Conform to Equatable and Hashable Protocols

To use the tag and selection arguments in a navigation link, your struct or class must conform to the Equatable and Hashable protocols. Your project won’t compile until you make the struct or class conform to those protocols.

Making your struct or class conform to Equatable requires you to implement the == operator to check for equality.

Replace Chapter with the name of your struct or class. Do whatever comparisons you need to make to determine that two objects are equal. SwiftUI list items require a unique ID. That’s what I used to determine equality in the example.

To conform to the Hashable protocol, you must implement the hash function.

Inside the hash function, call the hasher’s combine function for each property in your struct or class. Supply the name of the property.

Add the .onDeleteCommand Handler to the List

The last step is to remove the item from the list. Add the .onDeleteCommand handler to the list to enable the Delete menu item in the Edit menu. Inside the block of code, you will use the selection variable you added to the list view to find the selection index. Use the selection index to remove the item from the list.

The firstIndex function returns the first selected item in the list. After getting the index of the selected item, remove that item from the array that populates the list.

Now let’s put the whole list view together.

Removing an Item with a Button and the Delete Key

At this point you can remove a list item by choosing Edit > Delete. But you may want to provide a button to remove an item. How do you remove list items by clicking a button?

Start by moving the code inside the .onDeleteCommand handler into its own function. Moving the deletion code into a separate function will also make the list code cleaner.

Now the .onDeleteCommand handler looks like the following:

Call the function in your button. Add a .keyboardShortcut handler to the button to remove list items with the Delete (Backspace) key.

I haven’t figured out how to add a keyboard shortcut for the Delete menu item in the Edit menu. Every example I’ve seen on creating keyboard shortcuts for menu items in SwiftUI uses custom menu items, not the menu items that Apple supplies. I’ll update the article if I ever find a solution.

Sample Project

I have a project on GitHub that supports removing items from lists. Look at the PageListView.swift and Wiki.swift files for the list removal code.

Another sample project for SwiftUI list item removal is the Feed Read project by TrozWare for the Back to the Mac conference.

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.



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

Saving Data When Your Swift 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.

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.


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:

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.


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.

How to Notarize a Mac App

Notarizing a Mac app allows it to work better with Apple’s Gatekeeper feature in macOS. If you do not notarize your app, when someone downloads the app and tries to run it, an alert will open saying the app is from an untrusted developer. This alert may make people afraid to run your app. Notarizing your app keeps that alert from appearing and makes it easier for people to run your app. In this article you will learn how to notarize your Mac app so you can distribute it from your own website.


You must have a paid developer account with Apple to notarize apps.

Your app must have the Hardened Runtime capability enabled to notarize it. Open the project editor and click the Signing & Capabilities button to see if you have enabled the Hardened Runtime capability

Hardened Runtime

High Level Notarization Steps

Take the following steps to notarize your Mac app:

  1. Archive your project in Xcode by choosing Product > Archive.
  2. Open Xcode’s Organizer window by choosing Window > Organizer.
  3. Select your archive from the list of archives.
  4. Distribute the app.
  5. Choose a distribution method.
  6. Choose a destination.
  7. Choose a code signing option.
  8. Review and upload.
  9. Wait for Apple to notarize.
  10. Export the notarized app.

I will go into more detail on Steps 3–10 in the rest of the article.

Finding Your Archive and Distributing

In Xcode 12 the Organizer window looks similar to the following screenshot:


In the upper left window is a menu with a list of your archived apps. Choose your app from the list. Click the Archives item in the Organizer sidebar to see a list of archives for your app. Select an archive from the list.

Click the Distribute App button on the right side of the Organizer to start the process of notarizing your app.

Choose a Distribution Method

When you click the Distribute App button, a sheet opens with a list of distribution methods.


To notarize your app and upload it to your website, you should choose Developer ID. Click the Next button.

Choose a Destination

The next step is to choose a destination.


There are two destination options: Upload and Export. You must choose Upload to notarize the app. Click the Next button.

At this point, another sheet may appear with a checkbox to receive symbolicated crash reports from Apple. Select that checkbox and click the Next button.

Choose a Signing Option

Apple provides two methods to manage the signing of your app: Automatic and Manual.


I recommend automatically manage signing. Click the Next button.

At this point you will be asked for your Mac user’s password. Enter it.

Review and Upload

Now you can review options for notarizing the app.


If you find a mistake, click the Previous button to go back and fix the mistake. When things look good, click the Upload button to upload your app to Apple to notarize.

Wait for Apple to Notarize

If the upload is successful, Apple will send you an email when they finish notarizing the app. Normally notarizing takes 20–30 minutes.

Export the Notarized App

When the app is notarized, a button with the title Export Notarized App appears on the right side of the Organizer. You may need to scroll the window to see the button. Click that button to export the app to a location on your Mac. When you finish exporting, you can upload the app to your website for people to download.

Customizing the About Box in Your Mac App

Every Mac app has an About box, which you can access by choosing AppName > About AppName. For a new Cocoa project, the About box looks similar to the following image:


How do you customize the About box? Add a file named Credits.rtf to your project. Fill the file with your app credits. When you build the project, the text you entered in the RTF file will appear in a box between the version number and the copyright notice in the About box.

Choose File > New > File in Xcode to add a new file to the project. The RTF File template is in the Resource section.

Select the Credits.rtf file to edit the credits.


Now build your project and choose AppName > About. Your About box should look similar to the following screenshot:


If your credits are not appearing in a white scroll view, make sure you make the text long enough to trigger the need for a scroll view. When I just added the developer credit in the screenshot, the Credits appeared without a scroll view, and it didn’t look good.

Working with Open and Save Panels in Mac Apps

My short article on changing the button title for open and save panels gave me the idea to write a longer article on working with open and save panels.

You May Not Need to Deal with Open and Save Panels

Apple’s document architecture gives you a lot of default behavior for free. You can write an app that opens and saves files without having to write any code for the open and save panels. The most common case of having to write code that involves open and save panels is importing and exporting data. For example an image editor would use a save panel to export the image to common file formats, such as GIF, JPEG, and PNG.

Creating the Panel

Creating the open or save panel is the easiest step. Call the constructor for NSOpenPanel or NSSavePanel.

Customizing the Panel’s Appearance

When you create an open or save panel, you get the default appearance, which works well most of the time. But if you’re using a save panel to export a file, you want the button to say Export instead of Save because you’re exporting a file. AppKit provides several ways to customize the appearance of open and save panels. Some of the most popular ways include the following:

  • The prompt property lets you change the text of the default button.
  • The title property lets you change the title of the panel.
  • The nameFieldLabel property lets you change the label text in front of the filename text field.
  • The nameFieldStringValue property lets you set the filename currently shown in the filename text field.
  • The directoryURL property lets you set the current directory for the panel.
  • The accessoryView property lets you add an accessory view. A common use of an accessory view is to show a popup button with a list of file types.
  • The allowedFileTypes property lets you limit the file types you can open or save.

Custom Appearance Example

As an example, I’m going to share the code for a custom save panel for an app I’m developing. In this project I’m using a save panel to publish an EPUB book.

Because the save panel is for publishing books, I changed the panel’s title, name field label, and prompt (button text) to let people know this panel is for publishing books. I’m publishing EPUB books so I’m limiting the file types to files with the epub extension.

The most confusing line of code is the line that sets the text field value to self.displayName. The display name is the filename of the document that I’m publishing.

Showing the Panel

There are two ways to show an open or save panel. The easiest way is to show a panel as a modeless window. Call the panel’s begin method and supply a closure.

The other way is to show the panel as a sheet inside the document window. Call the panel’s beginSheetModal method. Supply the window and a closure.

Handling the Default Button Click

After showing the panel, you must handle the clicking of the default button (Open for an open panel, Save for a save panel). What you must do is check if the result of the panel running is NSFileHandlingPanelOKButton, which occurs when someone clicks the default button. Usually you will call a function that performs the task you want to do. For example if you were exporting a file, you would call a function that exports the file.

The Full Example

The following code shows the full example of using a save panel to publish a book:


The code begins by calling the buildPublishSavePanel function I wrote earlier to create the save panel and customize how it looks.

The next line of code calls the save panel’s begin method to show the save panel. The closure in the begin method uses a variable, result that stores the result of someone interacting with the save panel.

The first if statement checks if the person clicks the default (Publish) button. If so, the second if statement runs.

The second if statement gets the URL of the location the person chose to store the published book and calls a function to publish an EPUB book at the supplied URL. In your app you would call a function you wrote that creates (examples: importing or exporting a file) a file at the given URL.

Changing the Button Title for Open and Save Panels

Open and save panels are how Mac apps let people open and save files. An open panel defaults to using Open as the text for the main button. A save panel defaults to using Save. The default values make sense, but suppose you want to use an open panel to import a file in your app. How do you change the button text from Open to Import?

Use the prompt property to change the button text. The following example shows how to change the button text in an open panel from Open to Import:

Look at the NSSavePanel class reference to see other ways to customize the appearance of save and open panels.

Adding Undo Support for NSTextView

Mac text views support undo and redo for text editing without you having to write any code. All you have to do is set the text view’s allowsUndo property to true, which you can set in the attributes inspector.


Select the Allows Undo checkbox to turn on the text view’s undo/redo support.