Day 73 - 100 Days of Swift

4 minute read

Project 21 (part 2)

Day 73 is the second part of the twenty-first project. You review what you learned about notifications and he gives you three challenges to reinforce the material. He challenges you to update didReceive to show a different alert controller depending on what action identifier is passed in. He challenges you to add another action called “Remind me later” that reschedules the notification for later. And he challenges you to add notifications to Project2 for every day of the week, unless the user has already played on a given day.

For the first challenge I just added a little helper function to present alerts:

1
2
3
4
5
6
7
8
9
func presentAlert(title: String? = nil, message: String? = nil) {
    let alertController = UIAlertController(title: title,
                                            message: message,
                                            preferredStyle: .alert)
    let okay = UIAlertAction(title: "Okay", style: .cancel)
    alertController.addAction(okay)

    present(alertController, animated: true)
}

And then called it where appropriate:

1
2
3
4
5
6
7
8
9
10
11
// In didReceive
switch response.actionIdentifier {
case UNNotificationDefaultActionIdentifier:
    presentAlert(title: "Default Action",
                 message: "Looks like you just tapped on the notification")
case "show":
    presentAlert(title: "Show Action",
                 message: "Looks like you tapped on the 'Tell Me More' button")
default:
    break
}

For the second, I changed scheduleLocal to take in a TimeInterval parameter so that I could customize how long it would wait. I couldn’t figure out how to get this to work with #selector though, so I just added a new @objc function that the bar button called:

1
2
3
4
5
6
7
8
9
10
11
12
@objc func scheduleInitial() {
    scheduleLocal()
}

func scheduleLocal(_ timeInterval: TimeInterval = 5) {

    // down where the trigger is made
    let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval,
                                                    repeats: false)

    center.add(request)
}

Then I added another action:

1
2
3
4
5
6
// In delay categories
let delay = UNNotificationAction(identifier: "delay",
                                 title: "Remind Me Later")
let category = UNNotificationCategory(identifier: "alarm",
                                      actions: [show, delay],
                                      intentIdentifiers: [])

And added a case to the switch statement in didReceive:

1
2
case "delay":
    scheduleLocal(10)

When it is all said and done, it looks like this with “Show Me More” presenting an alert and “Remind Me Later” rescheduling the notification for 10 seconds in the future:

Screenshots of the updates to Project 21

For the third challenge I just added a couple of helper functions to the AppDelegate. In a real app, I probably wouldn’t do this here, and I would definitely handle rejection of permission more gracefully, but I just wanted to get it up and running. First, I have a function to ask for permission:

1
2
3
4
5
6
7
8
9
func askForNotificationPermission() {
    let center = UNUserNotificationCenter.current()
    let options: UNAuthorizationOptions = [.alert, .badge, .sound]
    center.requestAuthorization(options: options) { (granted, _) in
        if !granted {
            // TODO: - Occasionally ask the user if they've changed their mind?
        }
    }
}

Then I have a function to schedule the notifications for every day but today and a function to actually build the notifications:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
func scheduleDailyNotifications() {
    let center = UNUserNotificationCenter.current()

    // Get rid of any old notifications
    center.removeAllPendingNotificationRequests()

    // Figure out what day of the week today is
    let calendar = Calendar.current
    let dateComponents = calendar.dateComponents([.weekday],
                                                 from: Date())
    guard let today = dateComponents.weekday else { return }

    // Get a set of all the other days
    let days = Set([1, 2, 3, 4, 5, 6, 7]).subtracting([today])

    // Build notifications for each of those days
    days.map(buildNotification)
        // And add them all to the notification center
        .forEach { center.add($0) }
}

func buildNotification(on weekday: Int) -> UNNotificationRequest {
    let content = UNMutableNotificationContent()
    content.title = "Don't forget to play"
    content.body = """
    You haven't played your game yet today!
    """
    content.sound = UNNotificationSound.default

    // Schedule for 8:30 pm
    var dateComponents = DateComponents()
    dateComponents.hour = 20
    dateComponents.minute = 30
    dateComponents.weekday = weekday
    let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents,
                                                repeats: true)

    let request = UNNotificationRequest(identifier: UUID().uuidString,
                                        content: content,
                                        trigger: trigger)
    return request
}

And that completes the challenge. There isn’t really much to show though, it just presents the notifications at 8:30 pm every day that the app hasn’t already been opened.

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