Home iOS & Swift Tutorials

Home Screen Quick Actions for iOS: Getting Started

Learn how to integrate Static and Dynamic Home Screen Quick Actions into your SwiftUI iOS app.

Version

  • Swift 5.5, iOS 15, Xcode 13

Quick actions are a great way to provide your users fast access to your app’s common functionality within the home screen. iOS 13 introduced the concept of quick actions, where a user can touch and hold an app icon to display a set of shortcuts or actions to perform right from the home screen.

All apps have quick actions to edit the home screen or delete the app by default:


A menu presented after long pressing the 'Note Buddy' app icon on the home screen with options to 'Edit' the home screen or 'Delete' the app.

Developers can also provide their own quick actions to provide users with powerful shortcuts to common app functionality. The iOS Camera app has actions to take different types of photos or to record a video. A shopping app might let you jump directly to your orders or wishlist, and a messaging app might show your favorite contacts so you can easily access them.

I’m sure you can think of ways that quick actions would benefit your users. In this tutorial you’re going to learn about:

  • Static quick actions, which are always available for your app.
  • Dynamic quick actions, which your app can define at runtime.
  • How to support both types of quick action in the sample project, a note-taking app called Note Buddy.

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.

Note Buddy is a note-taking app with some basic functionality:

  • Add, edit or delete notes with a title and body.
  • Favorite a note.
  • Store your notes between launches.
  • Auto-sort notes by last modified date.

Open the project, then build and run. You’ll see this note page:


Screenshot of Starter project 'Note Buddy' after first launch showing a list of default notes and an option to create a new note.

To power Note Buddy’s functionality, you’ll find the Note model and its associated NoteStore class in the Models group.

Moving on to SwiftUI, in the Views group, you have:

  • NoteList: Shows a list of all your notes sorted by last modified date.
  • NoteRow: The view for a note row within the note list. It shows the note’s title, body and whether the user marked it as a favorite.
  • EditNote: An editor for modifying your note’s title, body or changing the favorite status.
  • NavigationLink+Value: A helper extension to make programmatic navigation a little easier when you have an associated data model to push.

Finally, AppMain describes your app with a reference to the NoteStore and a body which sets up the NoteList view within a WindowGroup and correctly injects the appropriate environment values.

Before you dive in to coding, it’s time to take a closer look at what the two types of quick actions are, and how they work.

Static vs. Dynamic Quick Actions

There are two types of quick actions available to you: static and dynamic.

You use static actions for actions that never change in your app, like the Mail app’s New Message action.

Use dynamic actions if your actions might change under certain conditions or depend on specific data or state. For example, the Messages app will add quick actions for all of your pinned conversations.

In both cases, you add code to handle a specific action that gets triggered. Since adding static quick actions is quicker, you’ll start with those.

Creating Static Quick Actions

Static quick actions are a great way to let your users create a new note.

A sheet of paper emoticon with a smiley face.

First, add model code to assist you in handling a triggered action. Right-click the Models group and then click New File…Swift File. Name your new file Action and click Create.

Replace the contents of the file with:

import UIKit

// 1
enum ActionType: String {
  case newNote = "NewNote"
}

// 2
enum Action: Equatable {
  case newNote

  // 3
  init?(shortcutItem: UIApplicationShortcutItem) {
    // 4
    guard let type = ActionType(rawValue: shortcutItem.type) else {
      return nil
    }

    // 5
    switch type {
    case .newNote:
      self = .newNote
    }
  }
}

// 6
class ActionService: ObservableObject {
  static let shared = ActionService()

  // 7
  @Published var action: Action?
}

A lot is going on. Here’s what you added:

  1. You create an enum called ActionType backed by a String. You’ll use the string values later to help identify different types of action your app will perform. The newNote case will identify the action for creating a New Note.
  2. You create another enum called Action that looks similar, but is just Equatable. This might look a little repetitive, but it’ll make sense when you add other actions later on.
  3. Then you create a failable initializer that accepts an instance of UIApplicationShortcutItem. The system uses this type to describe different quick actions.
  4. Here, you ensure that you’re creating an Action for a known ActionType, otherwise you return nil.
  5. Switch on the different possible ActionType values known to your app. You can then use the information available to describe the correct Action.
  6. Define an ObservableObject class you can later pass into the SwiftUI environment as well as provide a singleton accessor for later when you work with UIKit code.
  7. Define a @Published property that can represent an action your app should perform.

The aim of the Action concept is for you to model quick actions your app supports and work with them safely when mapping between UIApplicationShortcutItem.

Now, open AppMain.swift. Above noteStore, add a new property for ActionService:

private let actionService = ActionService.shared

Then update the body implementation to ensure that the service is injected into the view hierarchy and that NoteList can access it:

var body: some Scene {
  WindowGroup {
    NoteList()
      .environmentObject(actionService)
      .environmentObject(noteStore)
      .environment(\.managedObjectContext, noteStore.container.viewContext)
  }
}

ActionService is available in the SwiftUI environment, and it’s time to use it. Open NoteList.swift in the Views group and add the following properties under noteStore:

@EnvironmentObject var actionService: ActionService
@Environment(\.scenePhase) var scenePhase

Not only are you accessing the ActionService class, you’ll also need access to the ScenePhase, which is a property that notifies you when your app becomes active and when it enters the background.

At the bottom of the view, add the following method:

func performActionIfNeeded() {
  // 1
  guard let action = actionService.action else { return }

  // 2
  switch action {
  case .newNote:
    createNewNote()
  }

  // 3
  actionService.action = nil
}

This method does three things:

  1. Checks if there’s an action in the ActionService.
  2. Switches on the type of action and invokes the createNewNote() action for Action.newNote.
  3. Removes the action from the ActionService since it was performed.

You need to trigger this code whenever your app becomes active. You’ll use the onChange(of:perform:) view modifier with the scenePhase property that you added earlier.

Add the following code after the closing brace of the toolbar modifier:

// 1
.onChange(of: scenePhase) { newValue in
  // 2
  switch newValue {
  case .active:
    performActionIfNeeded()
  // 3
  default:
    break
  }
}

Here, the code:

  1. Adds a modifier to the List that will fire its closure whenever the scenePhase changes.
  2. Using the argument provided, it switches on the value. If it’s .active, it’ll call your new performActionIfNeeded() method.
  3. Since you don’t care about the other states, such as .inactive or .background, it doesn’t do anything.

Modifying the Info Property List File

You added logic to your code that handles the action. But before running the app, you still have to tell the system about your static action. This is simple to do.

In the Resources group, open Info.plist. Right click Information Property List at the top and click Add Row.

In the Key column, type Home Screen Shortcut Items and press return to finish editing. Xcode automatically sets the Type to Array and adds an item for you. Expand the elements by clicking the chevron and you’ll see two nested keys:


Xcode's Info.plist GUI showing the 'Home Screen Shortcut Items' property and its default nested values

Each item listed in this array represents a single static quick action that your app supports. Update the placeholder item with the following values:

  • Shortcut Item Type: NewNote
  • Title: New Note

In addition to the default keys, you need to add one more. Hover over Title and click the + icon that appears. Type UIApplicationShortcutItemIconSymbolName (ignore the pop-up options for now, you have to type the full string) for the Key and square.and.pencil for the Value.

Here’s a breakdown of each of these keys:

  • Shortcut Item Type: A string identifier that represents a unique type of action. You might notice that this matches the raw value of the ActionType.newNote enum case you added earlier. That’s important.
  • Title: The user-friendly title that displays when the user taps and holds your app icon.
  • UIApplicationShortcutItemIconSymbolName: The SF Symbol to use for this action. You can also use UIApplicationShortcutItemIconFile
    for an icon file available in your bundle, or UIApplicationShortcutItemIconType for a set of pre-defined options available for quick actions. Icon file and icon type are selectable from the drop down list when editing the Info.plist entry, but the symbol option is not.


Xcode's Info.plist GUI showing the 'Home Screen Shortcut Items' property with the values for the 'New Note' shortcut

There are additional keys available for shortcut items in your Info.plist that you can read about in the Apple documention.

Building and Running Your App

Build and run your app. Go back to the home screen and long-press the Note Buddy app icon to see your quick action now available in the list:


The Quick Action's menu for 'Note Buddy' now including the 'New Note' static action

Tap New Note to see what happens:


The list of notes screen in Note Buddy.

Nothing?!?

Well, that’s weird. You added a model to work with actions within the app and defined a static action in Info.plist.

What’s wrong, then?

The system tells your app when the user interacts with a quick action, but you’re not doing anything with the information yet. Time to do that!

Handling Static Quick Actions

SwiftUI doesn’t yet provide any native mechanisms to respond to launch events from quick actions. Therefore, you need to rely on UIKit for this part.

Right-click AppMain.swift and select New File…Swift File. Name the file AppDelegate and click Create. Replace the contents of the file with:

// 1
import UIKit

// 2
class AppDelegate: NSObject, UIApplicationDelegate {
  private let actionService = ActionService.shared

  // 3
  func application(
    _ application: UIApplication,
    configurationForConnecting connectingSceneSession: UISceneSession,
    options: UIScene.ConnectionOptions
  ) -> UISceneConfiguration {
    // 4
    if let shortcutItem = options.shortcutItem {
      actionService.action = Action(shortcutItem: shortcutItem)
    }

    // 5
    let configuration = UISceneConfiguration(
      name: connectingSceneSession.configuration.name,
      sessionRole: connectingSceneSession.role
    )
    configuration.delegateClass = SceneDelegate.self
    return configuration
  }
}

// 6
class SceneDelegate: NSObject, UIWindowSceneDelegate {
  private let actionService = ActionService.shared

  // 7
  func windowScene(
    _ windowScene: UIWindowScene,
    performActionFor shortcutItem: UIApplicationShortcutItem,
    completionHandler: @escaping (Bool) -> Void
  ) {
    // 8
    actionService.action = Action(shortcutItem: shortcutItem)
    completionHandler(true)
  }
}

If you’re not too familiar with UIKit, don’t worry! Here’s an overview of the code above:

  1. Import the UIKit framework to access the symbols and types you need in this file.
  2. Create a class named AppDelegate that inherits from NSObject and conforms to the UIApplicationDelegate protocol.
  3. Implement application(_:configurationForConnecting:options:) to hook into an event triggered when the app is preparing to launch your main UI.
  4. Unwrap shortcutItem provided with the options. If it’s present, this indicates that the user is launching your app from a quick action. Use the initializer you added earlier to map the data to an Action and assign it to the ActionService.
  5. Fulfill the requirements of the method by creating the appropriate UISceneConfiguration object and returning it.
  6. Similar to step two, create another class that conforms to the UIWindowSceneDelegate protocol.
  7. Implement windowScene(_:performActionFor:completionHandler:) to hook into events that trigger when a user interacts with a quick action after your app has already launched, for example, when it’s in the background.
  8. Similarly to step four, attempt to convert UIApplicationShortcutItem into an Action and pass it onto the ActionService.

Finally, your SwiftUI app needs to use the new AppDelegate and SceneDelegate. So, in AppMain.swift, underneath the actionService, add the following property inside AppMain:

@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

Time to build and run your app again. Head back to the home screen and try using your quick action again:


Animated clip of the Quick Action launching Note Buddy and creating a new note

Fantastic! You added your first static quick action. While this opens the door to many possibilities, you can’t customize it with information used at runtime. That’s where dynamic actions come in.

Creating Dynamic Quick Actions

If users want to edit their most recent note, they still have to open the app, scroll through the list to find it, tap it and then edit.

It would be great to simplify and speed up the process!

While static actions go in Info.plist, you add all dynamic actions in code. In the case of Note Buddy, and likely for many of your apps, these dynamic actions will be added when your scene’s phase changes to background. Thus, before heading to the home screen, you set up some actions to edit the most recent notes by the last modified date.

Handling Dynamic Quick Actions

To add dynamic actions, first add a new case to your ActionType and Action enums.

Open Action.swift and add the following to ActionType:

case editNote = "EditNote"

Add the following new case to Action:

case editNote(identifier: String)

This is why ActionType and Action needed to be two separate enums. While ActionType represents the identifiers of different quick action types, Action represents the action itself. In some cases, like the case of .editNote, this can include other associated values.

After adding the new enum cases, you’ll notice that the compiler has kindly told you that the switch statement in init?(shortcutItem:) is no longer exhaustive. Fix that by adding the following case to the switch:

case .editNote:
  if let identifier = shortcutItem.userInfo?["NoteID"] as? String {
    self = .editNote(identifier: identifier)
  } else {
    return nil
  }

Here you’ll check the shortcutItem‘s userInfo dictionary to get the ID of the note to edit. If successful, you initialize the action as .editNote with the associated value. Otherwise, the initializer just fails by returning nil.

If you try to build the project now, you’ll see one more compile failure in NoteList.swift. Head over to it, and you’ll find that you also need to implement the new action handling inside performActionIfNeeded().

Add the following code to the switch statement:

case .editNote(let identifier):
  selectedNote = noteStore.findNote(withIdentifier: identifier)

Build and run your app. Then head to the home screen, long-press the Note Buddy icon and see the results:


Note Buddy's Quick Actions menu with only an option for 'New Note'.

Once again…nothing?

While you already made sure that quick actions pass to your app correctly, you still haven’t told your app to show any dynamic items. You’ll do that next.

Adding Your Dynamic Quick Actions

As mentioned earlier, a good place to add your dynamic actions is when the app goes into the background. At this point, you have all the information needed to run the code that determines what actions to add and then add them.

Since you’re already observing the ScenePhase in NoteList, that’s a good place to update the actions. But before you do that, you need to create your own instances of UIApplicationShortcutItem that describe your dynamic action.

Head to Note.swift and add the following import at the top of the file:

import UIKit

Inside the Note extension, add the following property:

// 1
var shortcutItem: UIApplicationShortcutItem? {
  // 2
  guard !wrappedTitle.isEmpty || !wrappedBody.isEmpty else { return nil }

  // 3
  return UIApplicationShortcutItem(
    type: ActionType.editNote.rawValue,
    localizedTitle: "Edit Note",
    localizedSubtitle: wrappedTitle.isEmpty ? wrappedBody : wrappedTitle,
    icon: .init(systemImageName: isFavorite ? "star" : "pencil"),
    userInfo: [
      "NoteID": identifier as NSString
    ]
  )
}

Here’s a code breakdown:

  1. You define a new computed property on Note called shortcutItem. The property is optional because you don’t need to represent every note as a quick action.
  2. If the note doesn’t have a title or body, you return nil so that this note won’t be represented.
  3. Then you initialize a new instance of UIApplicationShortcutItem and return it.
    • Use the type defined by ActionType so it matches the expected value when you read it back later.
    • Provide a user-friendly title, like you did in Info.plist.
    • To personalize the action some more, you include either the title or body of the note as the subtitle of the quick action.
    • Use an appropriate SF Symbol to represent if the item is a favorite or not.
    • Include the unique identifier of the Note in the userInfo so that you’ll know which note to edit when you respond to the quick action.

Now that you created a property to expose the appropriate shortcut for a given Note, navigate back to NoteList.swift. Add the following method beneath performActionIfNeeded():

func updateShortcutItems() {
  UIApplication.shared.shortcutItems = notes.compactMap(\.shortcutItem)
}

Here you compact map the notes array to produce an array of just the non-nil UIApplicationShortcutItem‘s. This array is then assigned to the UIApplication‘s shortcutItems property which, in turn, becomes available alongside your static actions in the Home Screen Menu.

Now, you just need to call this method whenever the app goes into the background. Scroll back up to the onChange(of: perform:) modifier that you added earlier and add the following inside of the switch statement:

case .background:
  updateShortcutItems()

Build and run. Then have a look to see which items are available:


Animated clip of the Quick Action launching Note Buddy and creating a new note

Yay! You’re done.

Grey smiley face emoticon.

Make some edits to your notes, favorite a few and repeat the process. The notes shown, as well as the order, stay perfectly in sync whenever you background the app:


Animated clip demonstrating how Quick Actions are updated to reflect in-app changes

You may have noticed that not all of your notes show up. iOS limits the number of actions to only show those that fit on-screen. However, the documentation suggests you don’t specifically limit the count of dynamic actions. Instead, do it as you have for Note Buddy.

With that, iOS will show all the quick actions it can fit. If future updates enable more, that’s fantastic as you don’t need to push out an app update to support adding more actions before you background the app.

Where to Go From Here?

Download the finished project by clicking Download Materials at the top or bottom of this tutorial.

And there you have it: You’re equipped and ready to add quick actions to your apps and projects.

If you’d like to know more, take a look at the Apple documentation for Menus and Shortcuts in UIKit, as well as the Human Interface Guidelines for Home Screen Quick Actions.

What other cool uses of quick actions can you think of? Thanks for following along. Leave your implementations and ideas in the comments.

Reviews

More like this

Contributors

Comments