Локальные уведомления в мобильном приложении: 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







