Day 72 - 100 Days of Swift

4 minute read

Project 21 (part 1)

Day 72 is the first part of the twenty-first project. It is a technique project where you look at posting local user notifications. You get a basic project set up. You ask the user for permission to send notifications. You look at scheduling notifications based on DateComponents and TimerIntervals. And you customize the actions associated with an alert and look at how to respond to them in the app.

First, you make a single view app, embed the default ViewController in a UINavigationController and add two bar button items:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// In viewDidLoad
navigationItem.leftBarButtonItem =
    UIBarButtonItem(title: "Register",
                    style: .plain,
                    target: self,
                    action: #selector(registerLocal))

navigationItem.rightBarButtonItem =
    UIBarButtonItem(title: "Schedule",
                    style: .plain,
                    target: self,
                    action: #selector(scheduleLocal))

@objc func registerLocal() {
}

@objc func scheduleLocal() {
}

Then you import UserNotifications and write the function to ask the user for their permission:

1
2
3
4
5
6
7
8
9
10
11
12
13
// At top of file
import UserNotifications

// In registerLocal
let center = UNUserNotificationCenter.current()
let options: UNAuthorizationOptions = [.alert, .badge, .sound]
center.requestAuthorization(options: options) { (granted, error) in
    if granted {
        print("Yay!")
    } else {
        print("D'oh")
    }
}

That presents the system alert asking the user for their permission if they haven’t already been asked, or just returns the granted status if they have already been asked.

Then you write the function to schedule a notification:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
registerCategories()
let center = UNUserNotificationCenter.current()

let content = UNMutableNotificationContent()
content.title = "Late wake up call"
content.body = """
The early bird catches the worm,
but the second mouse gets the cheese.
"""
content.categoryIdentifier = "alarm"
content.userInfo = ["customData": "fizzbuzz"]
content.sound = UNNotificationSound.default

// This is how you would do DateComponents,
// but it is easier to test a TimerInterval
// var dateComponents = DateComponents()
// dateComponents.hour = 10
// dateComponents.minute = 30
// let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents,
//                                             repeats: true)

let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5,
                                                repeats: false)

let request = UNNotificationRequest(identifier: UUID().uuidString,
                                    content: content,
                                    trigger: trigger)
center.add(request)

Notifications need an identifier, which should be a unique string, some content, which is what you want to display, and a trigger, which is when it should be sent. The trigger can be based on DateComponents, so a certain hour of the day or day of the week or whatever, or a TimerInterval which is some amount of time from now, or a geofence, meaning when the user leaves or enters some location.

Then you have this view controller adopt UNUserNotificationCenterDelegate and add a registerCategories method, and call it in scheduleLocal. This is where we will let the system know what actions we want the notification to have:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// In registerCategories
let center = UNUserNotificationCenter.current()
center.delegate = self

let show = UNNotificationAction(identifier: "show",
                                title: "Tell me more…",
                                options: .foreground)
let category = UNNotificationCategory(identifier: "alarm",
                                      actions: [show],
                                      intentIdentifiers: [])

center.setNotificationCategories([category])

// At the beginning of scheduleLocal
registerCategories()

Here we add an action called show with the title “Tell me more…” that will open the app in the foreground.

Finally, you implement a UNUserNotificationCenterDelegate method when the center receives a response:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func userNotificationCenter(_ center: UNUserNotificationCenter,
                            didReceive response: UNNotificationResponse,
                            withCompletionHandler completionHandler: @escaping () -> Void) {
    let userInfo = response.notification.request.content.userInfo
    if let customData = userInfo["customData"] as? String {
        print("Custom data received: \(customData)")

        switch response.actionIdentifier {
        case UNNotificationDefaultActionIdentifier:
            print("Default identifier")
        case "show":
            print("Show more information")
        default:
            break
        }
    }

    completionHandler()
}

This is mostly just to show the structure of what you would do because all we’re doing here is printing out the data. But we check see if we have the customData and if so print it out. Then we check the actionIdentifier and if it is the show action we made, we’re going to do one thing, and if it isn’t, we’re going to do something else.

When it is all said and done it looks like this:

Screenshots of working app.

You can find my version of this project at the end of day 72 on Github here.