SkillAgentSearch skills...

NiceNotifications

🔔 Create rich local notifications experiences on iOS with incredible ease

Install / Use

/learn @dreymonde/NiceNotifications
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

NiceNotifications

<img src="_Media/icon.png" width="70">

NiceNotifications reimagines local notifications on Apple platforms.

It gives developers a new way to manage notification scheduling, permissions and grouping.

At its most basic form, it helps to schedule local notifications easily, in a declarative way.

At its most advanced, it introduces a whole new way of looking at local notifications, with the concept of "Notification Timelines", similar to WidgetKit or ClockKit APIs.

Author & maintainer: @dreymonde

WARNING! As of now, NiceNotifications is in early beta. Some APIs is likely to change between releases. Breaking changes are to be expected. Feedback on the API is very welcome!

Showcase

import NiceNotifications

LocalNotifications.schedule(permissionStrategy: .askSystemPermissionIfNeeded) {
    EveryMonth(forMonths: 12, starting: .thisMonth)
        .first(.friday)
        .at(hour: 20, minute: 15)
        .schedule(title: "First Friday", body: "Oakland let's go!")
}

Installation

Swift Package Manager

  1. Click File → Swift Packages → Add Package Dependency.
  2. Enter http://github.com/nicephoton/NiceNotifications.git

Basics Guide

Scheduling a one-off notification

// `NotificationContent` is a subclass of `UNNotificationContent`.
// You can also use `UNNotificationContent` directly
let content = NotificationContent(
    title: "Test Notification",
    body: "This one is for a README",
    sound: .default
)

LocalNotifications.schedule(
    content: content,
    at: Tomorrow().at(hour: 20, minute: 15),
    permissionStrategy: .scheduleIfSystemAllowed
)

What is permissionStrategy?

In most cases, NiceNotifications will handle all the permission stuff for you. So you can feel free to schedule notifications at any time, and permission strategy will take care of permissions.

Basic permission strategies:

  1. askSystemPermissionIfNeeded - if the permission was already given, will proceed to schedule. If the permission was not yet asked, it will ask for system permission, and then proceed if successful. If the permission was rejected previously, it will not proceed.
  2. scheduleIfSystemAllowed - will only proceed to schedule if the permission was already given before. Otherwise, will do nothing.

What is Tomorrow().at( ... )?

NiceNotifications uses DateBuilder to help define notification trigger dates in a simple, clear and easily readable way. Please refer to DateBuilder README for full details.

Here's a short reference:

Today()
    .at(hour: 20, minute: 15)

NextWeek()
    .weekday(.saturday)
    .at(hour: 18, minute: 50)

EveryWeek(forWeeks: 10, starting: .thisWeek)
    .weekendStartDay
    .at(hour: 9, minute: 00)

EveryDay(forDays: 30, starting: .today)
    .at(hour: 19, minute: 15)
    
ExactlyAt(account.createdAt)
    .addingDays(15)
    
WeekOf(account.createdAt)
    .addingWeeks(1)
    .lastDay
    .at(hour: 10, minute: 00)

EveryMonth(forMonths: 12, starting: .thisMonth)
    .lastDay
    .at(hour: 23, minute: 50)

NextYear().addingYears(2)
    .firstMonth.addingMonths(3) // April (in Gregorian)
    .first(.thursday)

ExactDay(year: 2020, month: 10, day: 5)
    .at(hour: 10, minute: 15)

ExactYear(year: 2020)
    .lastMonth
    .lastDay

Scheduling multiple notifications

LocalNotifications.schedule(permissionStrategy: .scheduleIfSystemAllowed) {
    Today()
        .at(hour: 20, minute: 30)
        .schedule(title: "Hello today", sound: .default)
    Tomorrow()
        .at(hour: 20, minute: 45)
        .schedule(title: "Hello tomorrow", sound: .default)
} completion: { result in
    if result.isSuccess {
        print("Scheduled!")
    }
}

Scheduling recurring notifications

WARNING! iOS only allows you to have no more than 64 scheduled local notifications, the rest will be silently discarded. (Docs)

func randomContent() -> NotificationContent {
    return NotificationContent(title: String(Int.random(in: 0...100)))
}

LocalNotifications.schedule(permissionStrategy: .askSystemPermissionIfNeeded) {
    EveryDay(forDays: 30, starting: .today)
        .at(hour: 20, minute: 30, second: 30)
        .schedule(with: randomContent)
}

For recurring content based on date:

func content(forTriggerDate date: Date) -> NotificationContent {
    // create content based on date
}

LocalNotifications.schedule(permissionStrategy: .askSystemPermissionIfNeeded) {
    EveryDay(forDays: 30, starting: .today)
        .at(hour: 20, minute: 30, second: 30)
        .schedule(with: content(forTriggerDate:))
}

Cancelling notification groups

let group = LocalNotifications.schedule(permissionStrategy: .askSystemPermissionIfNeeded) {
    EveryDay(forDays: 30, starting: .today)
        .at(hour: 15, minute: 30)
        .schedule(title: "Hello!")
}

// later:

LocalNotifications.remove(group: group)

Asking permission without scheduling

LocalNotifications.requestPermission(strategy: .askSystemPermissionIfNeeded) { success in
    if success {
        print("Allowed")
    }
}

Getting current system permission status

LocalNotifications.SystemAuthorization.getCurrent { status in
    switch status {
    case .allowed:
        print("allowed")
    case .deniedNow:
        print("denied")
    case .deniedPreviously:
        print("denied and needs to enable in settings")
    case .undetermined:
        print("not asked yet")
    }
    if status.isAllowed {
        print("can schedule!")
    }
}

Scheduling directly with UNNotificationRequest

If you just want to use the permission portion of NiceNotifications and create UNNotificationRequest instances yourself, use .directSchedule function:

let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 360, repeats: true)

let content = UNMutableNotificationContent()
content.title = "Repeating"
content.body = "Every 6 minutes"
content.sound = .default

let request = UNNotificationRequest(
    identifier: "repeating_360",
    content: content,
    trigger: trigger
)

LocalNotifications.directSchedule(
    request: request,
    permissionStrategy: .askSystemPermissionIfNeeded
) // completion is optional

Advanced Guide

Notification Timelines

The most powerful feature of NiceNotifications is timelines within notification groups, which lets you describe your entire local notifications experience in a WidgetKit-like manner.

Case study: "Daily Quote" notifications

Let's say we have an app that shows a different quote from a list every morning. The user can also disable / enable certain quotes, or add their own.

For that, we need to define a new class that implements LocalNotificationsGroup protocol:

public protocol LocalNotificationsGroup {
    var groupIdentifier: String { get }    
    func getTimeline(completion: @escaping (NotificationsTimeline) -> ())
}

Groups not only allow you to have clear logical separation between different experiences, but to also have user permission on a per group basis (we'll get to that later).

Let's implement our DailyQuoteGroup:

final class DailyQuoteGroup: LocalNotificationsGroup {
    let groupIdentifier: String = "dailyQuote"

    func getTimeline(completion: @escaping (NotificationsTimeline) -> ()) {
        let timeline = NotificationsTimeline {
            EveryDay(forDays: 50, starting: .today)
                .at(hour: 9, minute: 00)
                .schedule(title: "Storms make oaks take deeper root.")
        }
        completion(timeline)
    }
}

But this will only give us 50 identical quotes for the next 50 days. Let's make it more interesting by giving a user an actual random quote each day:

final class DailyQuoteGroup: LocalNotificationsGroup {
    let groupIdentifier: String = "dailyQuote"

    func getTimeline(completion: @escaping (NotificationsTimeline) -> ()) {
        let timeline = NotificationsTimeline {
            EveryDay(forDays: 50, starting: .today)
                .at(hour: 9, minute: 00)
                .schedule(with: makeRandomQuoteContent)
        }
        completion(timeline)
    }

    private func makeRandomQuoteContent() -> NotificationContent? {
        guard let randomQuote = QuoteStore.enabledQuotes.randomElement() else {
            return nil
        }

        return NotificationContent(
            title: randomQuote,
            body: "Tap here for more daily inspiration"
        )
    }
}

Looks great! Every time makeRandomQuoteContent gets invoked, we'll get a different quote, which is exactly what we want.

Okay, so what do we do with it now?

"Rescheduling" notification groups

Scheduling notification groups is easy:

LocalNotifications.reschedule(
    group: DailyQuoteGroup(),
    permissionStrategy: .askSystemPermissionIfNeeded
) // completion is optional

Why is it called "reschedule"? Because every time we inkove this function with the same group, the whole timeline will be cleaned and recreated.

Why is it useful? First of all, let's say that the user has disabled one of the quotes from showing up. But it might've been already scheduled! Not a problem: we'll simply call reschedule again, and it will no longer show up:

QuoteStore.disableQuote(userDisabledQuote)

LocalNotifications.reschedule(
    group: DailyQuoteGroup(),
    permissionStrategy: .scheduleIfSystemAllowed
)

Since DailyQuoteGroup uses QuoteStore.enabledQuotes to generate a random quote, newly rescheduled group will not have a disabled quote anymore!

Secondly, you've noticed that we've only scheduled for 50 days since "today". This is because we cannot use system recurring not

View on GitHub
GitHub Stars282
CategoryDevelopment
Updated4d ago
Forks12

Languages

Swift

Security Score

95/100

Audited on Mar 27, 2026

No findings