Как я могу удалить ранее доставленные уведомления, когда новое уведомление приходит с UNUserNotificationCenterDelegate в iOS 10?

Этот вопрос касается нового UserNotifications фреймворка в iOS 10.

У меня есть приложение, которое планирует локальное уведомление каждые полчаса после того, как пользователь выполнил определенное действие в приложении, начиная с 1 часа.

Чтобы не загромождать экран блокировки пользователя или центр уведомлений, я хочу, чтобы одновременно отображалось только одно уведомление, поэтому есть только одно уведомление с самой последней актуальной информацией. Мой план для достижения этого - удалять все доставленные уведомления каждый раз, когда появляется новое уведомление.

Кажется, это должно быть возможно с новым willPresent методом UNUserNotificationCenterDelegate, но он ведет себя не так, как я ожидал.

Вот функция, которую я вызываю для настройки всех уведомлений, начиная через 1 час после события в приложении и планируя уведомление каждые полчаса до последнего в 23,5 часа после события:

func updateNotifications() {

    for hour in 1...23 {
        scheduleNotification(withOffsetInHours: Double(hour))
        scheduleNotification(withOffsetInHours: Double(hour) + 0.5)
    }
}

Это функция, которая фактически планирует уведомления на основе mostRecentEventDate, который Date установлен в другом месте:

func scheduleNotification(withOffsetInHours: Double) {

    // set up a Date for when the notification should fire
    let offsetInSeconds = 60 * 60 * withOffsetInHours
    let offsetFireDate = mostRecentEventDate.addingTimeInterval(offsetInSeconds)

    // set up the content of the notification
    let content = UNMutableNotificationContent()
    content.categoryIdentifier = "reminder"
    content.sound = UNNotificationSound.default()
    content.title = "Attention!"
    content.body = "It has been \(withOffsetInHours) hours since the most recent event."

    // set up the trigger
    let triggerDateComponents = Calendar.current.components([.year, .month, .day, .hour, .minute, .second], from: offsetFireDate)
    let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDateComponents, repeats: false)

    // set up the request
    let identifier = "reminder\(withOffsetInHours)"
    let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)

    // add the request for this notification
    UNUserNotificationCenter.current().add(request, withCompletionHandler: { (error) in

        if error != nil {

            print(error)
        }
    })
}

В моем UNUserNotificationCenterDelegate метод willPresent настроен следующим образом:

func userNotificationCenter(_: UNUserNotificationCenter, willPresent: UNNotification, withCompletionHandler: (UNNotificationPresentationOptions) -> Void) {

    print("will present...")

    UNUserNotificationCenter.current().removeAllDeliveredNotifications()

    withCompletionHandler([.alert,.sound])
}

Я знаю, что вызывается функция willPresent, потому что она печатает «представит ...», но существующие уведомления в центре уведомлений не удаляются. Кто-нибудь знает, почему это не работает? Или есть способ заставить его работать так, как я хочу?


РЕДАКТИРОВАТЬ: Я придумал альтернативный подход для достижения того же результата, но он, похоже, тоже не работает.

Моя идея заключалась в том, чтобы использовать willPresent для отключения входящего запланированного уведомления при планировании немедленной доставки другого уведомления (без триггера). Все уведомления, которые должны прибыть немедленно, имеют один и тот же идентификатор, поэтому существующее уведомление с этим идентификатором всегда следует заменять, как в примере около 20:00 в этот доклад WWDC 2016 года о новой структуре UserNotifications. Вот мой обновленный метод willPresent:

func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent: UNNotification, withCompletionHandler: (UNNotificationPresentationOptions) -> Void) {

    let identifier = willPresent.request.identifier

    if identifier != "reminder" {

        let offsetInHoursString = identifier.replacingOccurrences(of: "reminder", with: "")

        let content = UNMutableNotificationContent()
        content.categoryIdentifier = "reminder"
        content.sound = UNNotificationSound.default()
        content.title = "Attention!"
        content.body = "It has been \(offsetInHoursString) hours since the most recent event."

        let identifier = "hydrationReminder"

        let request = UNNotificationRequest(identifier: identifier, content: content, trigger: nil)

        center.add(request, withCompletionHandler: { (error) in

            if error != nil {

                print(error)
            }
        })

        withCompletionHandler([])

    } else {

        withCompletionHandler([.alert,.sound])
    }
}

РЕДАКТИРОВАТЬ: я наконец понял, что willPresent будет только вызываться, если приложение находится на переднем плане, как сказано прямо в верхней части эта страница, поэтому ни один из этих подходов не должен работать. Я думал, что willPresent будет вызываться каждый раз при получении уведомления. Вернемся к чертежной доске с идеей «только самое новое и самое актуальное уведомление» ...


ОБНОВЛЕНИЕ (июль 2018 г.): с введением сгруппированных уведомлений в iOS 12 обновление старых уведомлений (с целью уменьшения беспорядка в уведомлениях) кажется менее актуальным. Я по-прежнему хотел бы иметь возможность сделать это, чтобы минимизировать появление беспорядка, и, похоже, должен быть паритет функций между удаленными push-уведомлениями, которые можно обновлять постфактум, и локальными уведомлениями, которые не могут быть обновлены позже. Однако, поскольку Apple представила сгруппированные уведомления, я бы ожидал, что они с меньшей вероятностью будут реализовывать возможность обновления старых локальных уведомлений в пользу того, чтобы приложения могли просто отправлять новые и группировать их вместе с существующими уведомлениями.


person gohnjanotis    schedule 12.08.2016    source источник
comment
Уже нашли решение? Точно такая же проблема!   -  person RJB    schedule 27.05.2018
comment
@RJB Это невозможно с текущими API   -  person gohnjanotis    schedule 27.05.2018


Ответы (2)


Вы можете проверить эту демонстрацию

Я думаю, вы хотите добиться того, чтобы функция называлась «уведомление об обновлении».

iOS 10 позволяет обновлять уведомления. Все, что вам нужно сделать, - оставить уведомлениям один и тот же идентификатор.

Посмотрим демо:

  1. первое уведомление:

    NSURL * imageUrl = [[NSBundle mainBundle] URLForResource:@"dog" withExtension:@"png"];
    UNNotificationAttachment *imgAtt = [UNNotificationAttachment attachmentWithIdentifier:@"image" URL:imageUrl options:nil error:&error];
    
    NSURL * mp4Url = [[NSBundle mainBundle] URLForResource:@"media" withExtension:@"mp4"];
    UNNotificationAttachment *mediaAtt = [UNNotificationAttachment attachmentWithIdentifier:@"image" URL:mp4Url options:nil error:&error];
    
    UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc]init];
    //在通知中心显示的总是第一个多媒体资源
    content.attachments = @[imgAtt,mediaAtt];
    content.badge = @1;
    content.title = @"Wake Up";
    content.subtitle = @"First time";
    content.body = @"next time。。。 ";
    content.categoryIdentifier = @"wakeup";
    content.launchImageName = @"dog";
    content.sound = [UNNotificationSound defaultSound];
//    content.threadIdentifier = @"";
    content.userInfo = @{@"first":@"5:00 am",@"second":@"6:00"};
    
    UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:5.0 repeats:NO];
    
    UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@"com.junglesong.pushtestdemo.wakeup" content:content trigger:trigger];
    
    [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
        NSLog(@"wake up message has been deliverd!");
    }];

  1. обновить первое уведомление:

    UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc]init];
    content.badge = @1;
    content.title = @"Update!dear,wake up";
    content.subtitle = @"Update! dear,please";
    content.body = @"Update!shall we have breakfast?";
    content.categoryIdentifier = @"wakeup";
    content.launchImageName = @"dog";
    content.sound = [UNNotificationSound defaultSound];
    //    content.threadIdentifier = @"";
    content.userInfo = @{@"first":@"5:00 am",@"second":@"6:00"};
    
    UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:5.0 repeats:NO];
    
    UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@"com.junglesong.pushtestdemo.wakeup" content:content trigger:trigger];
    
    [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
        NSLog(@"wake up message has been updated!");
    }];

Теперь вы добавляете два уведомления, но система обрабатывает их как одно и то же. Таким образом, есть только одно, а второе заменяет первое. В iOS 10 вызовите это обновление.

Свойство «идентификатор» - это идентификатор UNNotificationRequest, который может различать уведомления.

person wenghengcong    schedule 26.09.2016
comment
Может показаться, что это может быть решением, однако, если я попытаюсь использовать тот же идентификатор таким образом, у меня будет только одно запланированное уведомление. Он содержит только информацию о последнем обновлении и показывает только один раз, а не несколько обновлений уведомлений, запланированных для отображения обновленного уведомления каждый раз, когда предполагается доставить новое. - person gohnjanotis; 27.09.2016

Вы можете просто использовать removeDeliveredNotifications (withIdentifiers :)

person Owen Zhao    schedule 20.08.2016
comment
Это правильная функция для очистки уведомлений, но проблема в ее запуске. Проблема здесь в том, что нет метода, который вызывается, когда доставлено неудаленное уведомление, и приложение не находится на переднем плане, поэтому некуда вызывать его, когда уведомление доставляется, для очистки старых уже доставленных уведомлений. - person gohnjanotis; 20.08.2016
comment
Уже нашли решение? Та же проблема. - person Justin; 17.04.2017
comment
@gohnjanotis не уверен, что это будет полезно, но посмотрите мой ответ здесь - person Honey; 05.06.2017
comment
@Justin Я не нашел решения. Я не думаю, что есть способ делать то, что я хочу, с текущими API. - person gohnjanotis; 05.06.2017
comment
@Honey Если вы предлагаете реализовать код очистки уведомлений в userNotificationCenter (_: didReceive: withCompletionHandler :), это не даст желаемых результатов, поскольку эта функция вызывается только тогда, когда пользователь выполняет действие с уведомлением. - person gohnjanotis; 05.06.2017