Локальні сповіщення в мобільному додатку: iOS та Android
Клієнт поскаржився, що нагадування в трекері звичок перестали працювати після оновлення iOS. Причина — перевищено ліміт у 64 сповіщення, і система мовчки їх видалила. Ми переписали планувальник із пріоритетною чергою: найближчі 64 тримаємо в системі, решту переплановуємо при кожному відкритті. Цей спосіб ми застосовували в більш ніж 30 проєктах. Локальні сповіщення — єдиний тип, що не потребує сервера. Додаток сам планує їх через системний API: за часом, за тригером календаря або при вході в геозону.
Ми — команда мобільних розробників з 5-річним досвідом і більш ніж 50 реалізованих проєктів. Сертифіковані спеціалісти з iOS та Android. Реалізуємо локальні сповіщення під ключ: від схеми тригерів до публікації в сторах. Оцінимо ваш проєкт за 1 день — просто напишіть нам. Замовте впровадження й отримайте гарантію на код 3 місяці.
Як обійти ліміт у 64 сповіщення на iOS?
UNUserNotificationCenter дозволяє запланувати не більше 64 сповіщень одночасно. Для трекерів звичок, будильників або календарів цього недостатньо. Рішення — динамічна черга. Зберігайте всі заплановані нагадування в локальній БД (Core Data або Realm). При запуску додатка вибирайте найближчі 64 і реєструйте їх. При спрацьовуванні або скасуванні — оновлюйте чергу. Користувач ніколи не дізнається про ліміт. Ось приклад планування за часом:
import UserNotifications
// 1. За часом (через N секунд)
let content = UNMutableNotificationContent()
content.title = "Нагадування про зустріч"
content.body = "Зустріч з командою через 15 хвилин"
content.sound = .default
content.badge = 1
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 900, repeats: false)
let request = UNNotificationRequest(identifier: "meeting-reminder-123", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request)
// 2. За датою/часом (повторюване щодня о 9:00)
var dateComponents = DateComponents()
dateComponents.hour = 9
dateComponents.minute = 0
let dailyTrigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
// 3. По геозоні
let region = CLCircularRegion(center: CLLocationCoordinate2D(latitude: 50.45, longitude: 30.52), radius: 200, identifier: "office-zone")
region.notifyOnEntry = true
region.notifyOnExit = false
let geoTrigger = UNLocationNotificationTrigger(region: region, repeats: false) Чому після перезавантаження Android сповіщення зникають?
AlarmManager скидається при вимкненні пристрою. Якщо не обробити BOOT_COMPLETED, всі нагадування зникнуть. Ми завжди додаємо BroadcastReceiver на BOOT_COMPLETED, який читає активні нагадування з Room і переплановує їх через AlarmManager. Для періодичних завдань без точного часу використовуємо WorkManager — він автоматично відновлюється після reboot. Ось приклад точного аларму:
val alarmManager = context.getSystemService(AlarmManager::class.java)
val intent = Intent(context, NotificationReceiver::class.java).apply {
putExtra("title", "Нагадування про зустріч")
putExtra("body", "Зустріч через 15 хвилин")
putExtra("notification_id", 123)
}
val pendingIntent = PendingIntent.getBroadcast(
context,
123,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
triggerAtMillis,
pendingIntent
)NotificationReceiver — BroadcastReceiver, який будує і показує сповіщення:
class NotificationReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val notification = NotificationCompat.Builder(context, "reminders_channel")
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(intent.getStringExtra("title"))
.setContentText(intent.getStringExtra("body"))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.build()
NotificationManagerCompat.from(context)
.notify(intent.getIntExtra("notification_id", 0), notification)
}
}На Android 12+ точні аларми потребують дозволу SCHEDULE_EXACT_ALARM. Для повторюваних — WorkManager з PeriodicWorkRequest простіше та надійніше.
Як виглядає покрокова реалізація?
- Проєктування схеми сповіщень. Визначаємо типи тригерів: час, календар, геозона. Для кожного — контент, звук, значок, категорія. Враховуємо сценарії: одноразові, повторювані, зі скасуванням.
-
Налаштування каналів. На Android створюємо канали (
NotificationChannel), на iOS — категорії (UNNotificationCategory). Це дозволяє користувачеві керувати важливістю та групуванням. - Розробка планувальника. На iOS —
UNUserNotificationCenterз пріоритетною чергою. На Android —AlarmManagerдля точних +WorkManagerдля періодичних. Додаємо обробкуBOOT_COMPLETED. - Тестування. Перевіряємо на реальних пристроях: сон, перезавантаження, регіон, ліміти. Використовуємо TestFlight та Firebase App Distribution.
- Оптимізація під стори. Враховуємо вимоги до дозволів (точні аларми, фонова геолокація) і оформляємо код для рев'ю.
Порівняння iOS та Android
| Параметр | iOS | Android |
|---|---|---|
| API | UNUserNotificationCenter |
AlarmManager + NotificationManager |
| Макс. запланованих | 64 | без ліміту (але залежить від пристрою) |
| Геозони | вбудований UNLocationNotificationTrigger |
GeofencingClient (Google Play Services) |
| Повторення | UNCalendarNotificationTrigger |
WorkManager або власний через AlarmManager |
| Обробка reboot | не потрібна (iOS сама відновлює) | обов'язковий BOOT_COMPLETED BroadCastReceiver |
Типові сценарії та їх реалізація
| Сценарій | iOS | Android |
|---|---|---|
| Нагадування через 15 хвилин | UNTimeIntervalNotificationTrigger |
AlarmManager.setExact |
| Щоденне о 9:00 | UNCalendarNotificationTrigger |
WorkManager з PeriodicWorkRequest |
| При вході в геозону (офіс) | UNLocationNotificationTrigger |
GeofencingClient з переходом ENTER |
| Нагадування з пріоритетом (термінове) | content.interruptionLevel = .timeSensitive |
NotificationCompat.PRIORITY_HIGH з каналом високого пріоритету |
Як обробити натискання на сповіщення?
На iOS потрібно реалізувати UNUserNotificationCenterDelegate і метод userNotificationCenter(_:didReceive:withCompletionHandler:). На Android — вказати PendingIntent з action, який відкриває потрібний екран за deep link (App Links або scheme). Проконсультуйтеся з нами щодо інтеграції — ми допоможемо налаштувати правильну навігацію.
Що входить у нашу роботу
- Проєктна документація: схеми тригерів, діаграми станів.
- Вихідний код з коментарями на Swift та Kotlin.
- Налаштування сертифікатів і ключів (APNs, Google Play Store).
- Тестовий білд через TestFlight/Firebase App Distribution.
- Допомога з публікацією та модерацією.
- Гарантія на код 3 місяці.
Строки
Реалізація базового функціоналу (час + календар) на одній платформі — 3 робочі дні. З геозонами та двоплатформенна версія — до 6 днів. Строки можуть змінюватися залежно від складності, але ми завжди даємо точну оцінку після аналізу вашого проєкту. Зв'яжіться з нами, щоб обговорити ваш проєкт і отримати оцінку. Замовте реалізацію локальних сповіщень прямо зараз — ваші користувачі точно не пропустять важливе.
Приклад обробки геозон на Android
val geofence = Geofence.Builder()
.setRequestId("office-zone")
.setCircularRegion(50.45, 30.52, 200f)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
.build()
val request = GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.addGeofence(geofence)
.build()
geofencingClient.addGeofences(request, geofencePendingIntent)Геозонні сповіщення потребують дозволу ACCESS_FINE_LOCATION і на Android 10+ — ACCESS_BACKGROUND_LOCATION. Останній — окремий запит, користувач повинен явно обрати «Дозволити завжди» в налаштуваннях.
https://developer.apple.com/documentation/usernotifications/handling_notifications_and_notification-related_actions







