0Pricing
Swift Academy · Lesson

Notification Actions and Categories

Add interactive actions to notifications.

Notification Actions and Categories is a free Swift Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Swift Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Interactive Notifications

Notifications can do more than display text — they can offer buttons. A user can reply to a message, accept an invite, or snooze a reminder without opening the app. This is built from actions grouped into categories.

import UserNotifications
// Category = a set of actions
// Each delivered notification names its category
// so the system shows the right buttons

Defining an Action

A UNNotificationAction has an identifier (you match on it later), a localized title shown on the button, and options like .destructive or .foreground (which launches the app).

import UserNotifications
let accept = UNNotificationAction(
    identifier: "ACCEPT",
    title: String(localized: "Accept"),
    options: [.foreground])
let decline = UNNotificationAction(
    identifier: "DECLINE",
    title: String(localized: "Decline"),
    options: [.destructive])

Grouping Into a Category

A UNNotificationCategory bundles actions under a category identifier. The order of actions in the array is the order of buttons. You register categories once at launch with the notification center.

import UserNotifications
let inviteCategory = UNNotificationCategory(
    identifier: "INVITE",
    actions: [accept, decline],
    intentIdentifiers: [],
    options: [])
UNUserNotificationCenter.current()
    .setNotificationCategories([inviteCategory])

Tagging a Notification With Its Category

For the buttons to appear, the notification's content must set categoryIdentifier to a registered category. For remote pushes, the server sends "category": "INVITE" inside the aps dictionary.

import UserNotifications
let content = UNMutableNotificationContent()
content.title = String(localized: "New invite")
content.body = String(localized: "Ada invited you")
content.categoryIdentifier = "INVITE"

Handling the Chosen Action

When the user taps a button, the delegate's didReceive response fires with response.actionIdentifier set to your action id. Switch on it to perform the right work.

import UserNotifications
func userNotificationCenter(
    _ center: UNUserNotificationCenter,
    didReceive response: UNNotificationResponse
) async {
    switch response.actionIdentifier {
    case "ACCEPT":  print("accepted")
    case "DECLINE": print("declined")
    default:        break
    }
}

Default and Dismiss Identifiers

Two system identifiers always exist: UNNotificationDefaultActionIdentifier means the user tapped the body to open the app, and UNNotificationDismissActionIdentifier means they dismissed it. Handle these alongside your custom actions.

import UserNotifications
func route(_ id: String) {
    switch id {
    case UNNotificationDefaultActionIdentifier:
        print("opened the app")
    case UNNotificationDismissActionIdentifier:
        print("dismissed")
    default:
        print("custom action", id)
    }
}

Text Input Actions

A UNTextInputNotificationAction shows an inline text field — perfect for quick replies. It adds a button title and placeholder for the input box.

import UserNotifications
let reply = UNTextInputNotificationAction(
    identifier: "REPLY",
    title: String(localized: "Reply"),
    options: [],
    textInputButtonTitle: String(localized: "Send"),
    textInputPlaceholder: String(localized: "Message"))

Reading the Typed Text

For a text action, the response is a UNTextInputNotificationResponse. Cast to it to read userText, the string the user typed.

import UserNotifications
func handle(_ response: UNNotificationResponse) {
    if let textResponse = response
        as? UNTextInputNotificationResponse {
        let message = textResponse.userText
        print("reply:", message)
    }
}

Category Options

Category options refine behavior: .customDismissAction delivers the dismiss event to your delegate, .hiddenPreviewsShowTitle reveals the title even when previews are hidden on the lock screen, and .allowInCarPlay permits display in CarPlay.

import UserNotifications
let category = UNNotificationCategory(
    identifier: "MESSAGE",
    actions: [reply],
    intentIdentifiers: [],
    options: [.customDismissAction,
              .hiddenPreviewsShowTitle])
_ = category

Background vs Foreground Actions

Without the .foreground option, an action runs your handler in the background without bringing the app forward — ideal for snooze or like. With .foreground, the app launches so you can show UI. Choose based on whether the user needs to see something.

import UserNotifications
let snooze = UNNotificationAction(
    identifier: "SNOOZE",
    title: String(localized: "Snooze"),
    options: []) // background, no app launch
let open = UNNotificationAction(
    identifier: "OPEN",
    title: String(localized: "Open"),
    options: [.foreground]) // launches app
_ = (snooze, open)

A Complete Reply Flow

Combine a text action, a category, and a handler that distinguishes the typed reply from a plain open. This pattern powers in-line messaging replies.

import UserNotifications
func handle(_ response: UNNotificationResponse) async {
    switch response.actionIdentifier {
    case "REPLY":
        let text = (response as?
            UNTextInputNotificationResponse)?.userText ?? ""
        print("send reply:", text)
    case UNNotificationDefaultActionIdentifier:
        print("open conversation")
    default:
        break
    }
}

Quick Check

Recall how the system knows which buttons to show.

Recap

You learned interactive notifications:

  • Build UNNotificationActions (including UNTextInputNotificationAction) and group them into a UNNotificationCategory.
  • Register categories at launch; set categoryIdentifier on content (or category in the push) to show the buttons.
  • Handle response.actionIdentifier, including the default and dismiss identifiers, and read userText for replies.
  • Omit .foreground for background actions like snooze.

Frequently asked questions

Is the “Notification Actions and Categories” lesson free?

Yes — the full text of “Notification Actions and Categories” is free to read here on the web, and the Swift Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Swift Academy course, upgrade to CoddyKit PRO.

What will I learn in “Notification Actions and Categories”?

Add interactive actions to notifications. You practise Swift Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Swift Academy?

No prior experience is required. Swift Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Notification Actions and Categories” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Swift Academy lesson?

Yes. Every Swift Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Registering for Push Notifications
  2. Handling Notification Payloads
  3. Background Tasks and Refresh
  4. Notification Actions and Categories
← Back to Swift Academy