如何每天在不同时间重复本地通知

Posted

技术标签:

【中文标题】如何每天在不同时间重复本地通知【英文标题】:How to repeat local notifications every day at different times 【发布时间】:2014-09-06 13:39:55 【问题描述】:

我正在开发一个祈祷应用程序,该应用程序使用户能够为祈祷时间设置警报(本地通知),即用户将应用程序设置为每天通知他进行晨祷,问题是每次祈祷的时间每天都在变化,所以应用程序在星期四通知用户的时间将与星期五的时间不同,我需要每天重复本地通知,但请根据每天的祈祷时间,谁能给我一个想法?

【问题讨论】:

你有没有想过这个问题?我最终计算了接下来 10 天的祈祷,并以这种方式安排通知。唯一的问题是大约有 50 个通知限制,所以我必须为用户设置另一个通知以打开我的应用程序,以便可以安排接下来的 10 天。不喜欢它,但我能想到的唯一方法。 ***.com/questions/9862261/… @TruMan1:你是想让用户设置时间,还是应用程序给用户设置时间?并且,应该在时间上增加多少时间?我很想提供一些实现,但我需要更多信息。 :) 应用程序自己设置时间。它通过获取当前日期和用户当前位置来做到这一点,然后将其填充到算法中以吐出 5 个新的祈祷时间。根据时间和地点的不同,每天的 5 次祈祷都是不同的(即使与前一天只有几分钟的差异)。所以设置祈祷时间的不是用户,而是应用程序中的算法。 所以,听起来您只需要当天(与算法收到的日期相同的日期)的警报,因为您无法预测用户在未来几天的位置。跨度> 【参考方案1】:

对此有几种可能的解决方案。使用一次安排有限数量的通知的方法可能更安全,因为 ios 只保留 64 个最快的通知:

一个应用只能有有限数量的预定通知;系统保留最快触发的 64 条通知(自动重新安排的通知计为单个通知)并丢弃其余通知。

来源:UILocalNotification 类参考

依赖使用传递给application:didFinishLaunchingWithOptions:UILocalNotification 也不是一个好主意,因为它仅在用户滑动通知时传递:

查看启动选项字典以确定您的应用启动的原因。 application:willFinishLaunchingWithOptions: 和 application:didFinishLaunchingWithOptions: 方法提供了一个字典,其中的键指示您的应用启动的原因。

响应本地通知启动的关键值为: UIApplicationLaunchOptionsLocalNotificationKey

来源:UIApplicationDelegate 类参考

选项 1:一次安排一天(代码如下)

处理通知安排的一种方法是向用户显示一个安排,其中当天的通知安排在应用程序首次打开时安排。

使用CustomNotificationManager 类来处理时间可变的通知(下面提供的代码)。在您的 AppDelegate 中,您可以将本地通知的处理委托给此类,这将安排当天的通知和第二天的固定时间通知,或者响应祈祷通知。

如果用户打开应用程序以响应祈祷通知,应用程序可以将用户引导至应用程序的适当部分。如果用户打开应用响应定时通知,应用会根据用户的日期和位置安排当天的本地通知。

选项 2(略微精简的方法,但为用户提供的内容较少)

另一种方法是简单地使用祈祷通知的应用启动来安排紧随其后的祈祷通知。但是,这不太可靠,并且不提供预览通知计划的功能。

通知管理器头文件

@interface CustomNotificationManager : NSObject

- (void) handleLocalNotification:(UILocalNotification *localNotification);

@end

通知管理器实现文件

#import "CustomNotificationManager.h"

#define CustomNotificationManager_FirstNotification @"firstNotification"

@implementation CustomNotificationManager

- (instancetype) init

    self = [super init];

    if (self) 

    

    return self;


- (void) handleLocalNotification:(UILocalNotification *)localNotification

    //Determine if this is the notification received at a fixed time,
    //  used to trigger the scheculing of today's notifications
    NSDictionary *notificationDict = [localNotification userInfo];
    if (notificationDict[CustomNotificationManager_FirstNotification]) 
        //TODO: use custom algorithm to create notification times, using today's date and location
        //Replace this line with use of algorithm
        NSArray *notificationTimes = [NSArray new];

        [self scheduleLocalNotifications:notificationTimes];
     else 
        //Handle a prayer notification
    



/**
 * Schedule local notifications for each time in the notificationTimes array.
 *
 * notificationTimes must be an array of NSTimeInterval values, set as intervalas
 * since 1970.
 */
- (void) scheduleLocalNotifications:(NSArray *)notificationTimes

    for (NSNumber *notificationTime in notificationTimes) 
        //Optional: create the user info for this notification
        NSDictionary *userInfo = @;

        //Create the local notification
        UILocalNotification *localNotification = [self createLocalNotificationWithFireTimeInterval:notificationTime
                                                                                       alertAction:@"View"
                                                                                         alertBody:@"It is time for your next prayer."
                                                                                          userInfo:userInfo];

        //Schedule the notification on the device
        [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
    

    /* Schedule a notification for the following day, to come before all other notifications.
     *
     * This notification will trigger the app to schedule notifications, when
     * the app is opened.
     */

    //Set a flag in the user info, to set a flag to let the app know that it needs to schedule notifications
    NSDictionary *userInfo = @ CustomNotificationManager_FirstNotification : @1 ;

    NSNumber *firstNotificationTimeInterval = [self firstNotificationTimeInterval];

    UILocalNotification *firstNotification = [self createLocalNotificationWithFireTimeInterval:firstNotificationTimeInterval
                                                                                   alertAction:@"View"
                                                                                     alertBody:@"View your prayer times for today."
                                                                                      userInfo:userInfo];

    //Schedule the notification on the device
    [[UIApplication sharedApplication] scheduleLocalNotification:firstNotification];


- (UILocalNotification *) createLocalNotificationWithFireTimeInterval:(NSNumber *)fireTimeInterval
                                                    alertAction:(NSString *)alertAction
                                                    alertBody:(NSString *)alertBody
                                                     userInfo:(NSDictionary *)userInfo


    UILocalNotification *localNotification = [[UILocalNotification alloc] init];
    if (!localNotification) 
        NSLog(@"Could not create a local notification.");
        return nil;
    

    //Set the delivery date and time of the notification
    long long notificationTime = [fireTimeInterval longLongValue];
    NSDate *notificationDate = [NSDate dateWithTimeIntervalSince1970:notificationTime];
    localNotification.fireDate = notificationDate;

    //Set the slider button text
    localNotification.alertAction = alertAction;

    //Set the alert body of the notification
    localNotification.alertBody = alertBody;

    //Set any userInfo, e.g. userID etc. (Useful for app with multi-user signin)
    //The userInfo is read in the AppDelegate, via application:didReceiveLocalNotification:
    localNotification.userInfo = userInfo;

    //Set the timezone, to allow for adjustment for when the user is traveling
    localNotification.timeZone = [NSTimeZone localTimeZone];

    return localNotification;


/**
 * Calculate and return a number with an NSTimeInterval for the fixed daily
 * notification time.
 */
- (NSNumber *) firstNotificationTimeInterval

    //Create a Gregorian calendar
    NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];

    //Date components for next day
    NSDateComponents *dateComps = [[NSDateComponents alloc] init];
    dateComps.day = 1;

    //Get a date for tomorrow, same time
    NSDate *today = [NSDate date];
    NSDate *tomorrow = [cal dateByAddingComponents:dateComps toDate:today options:0];

    //Date components for the date elements to be preserved, when we change the hour
    NSDateComponents *preservedComps = [cal components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:tomorrow];
    preservedComps.hour = 5;
    tomorrow = [cal dateFromComponents:preservedComps];

    NSTimeInterval notificationTimeInterval = [tomorrow timeIntervalSince1970];

    NSNumber *notificationTimeIntervalNum = [NSNumber numberWithLongLong:notificationTimeInterval];

    return notificationTimeIntervalNum;


@end

AppDelegate didReceiveLocalNotification 实现

- (void) application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification

    CustomNotificationManager *notificationManager = [[CustomNotificationManager alloc] init];
    [notificationManager handleLocalNotification:notification];

可能的修改建议:如果 CustomNotificationManager 需要维护状态,您可以将其转换为 Singleton。

【讨论】:

【参考方案2】:

所以问题出现了,您需要不时设置此本地通知,但不能是可重复的通知。我假设用户设置了祈祷时间,并希望得到通知。我建议你设置一些,因为你从列表中知道。然后将后台获取设置为假设每 5 小时,并在应用程序后台启动时,只需检查仍然设置了哪些本地通知,并根据当前日期相应地更新列表。在这种情况下,后台获取不会每 5 小时准确地唤醒您的应用,但会尽力而为。我确信您的应用每天至少会唤醒两次。您可以根据需要调整时间。

机会主义地获取少量内容 需要定期检查新内容的应用程序可以要求系统唤醒它们,以便它们可以启动对该内容的获取操作。要支持此模式,请从 Xcode 项目的 Capabilities 选项卡的 Background mode 部分启用 Background fetch 选项。 (您也可以通过在应用程序的 Info.plist 文件中包含带有获取值的 UIBackgroundModes 键来启用此支持。)启用此模式并不能保证系统会给您的应用程序任何时间执行后台获取。系统必须平衡您的应用程序获取内容的需求与其他应用程序和系统本身的需求。在评估了这些信息后,系统会在有好的机会时为应用程序留出时间。当一个好机会出现时,系统会唤醒或启动您的应用程序到后台并调用应用程序委托的 application:performFetchWithCompletionHandler: 方法。使用该方法检查新内容并在内容可用时启动下载操作。完成下载新内容后,您必须立即执行提供的完成处理程序块,传递指示内容是否可用的结果。执行此块会告诉系统它可以将您的应用程序移回挂起状态并评估其电源使用情况。快速下载少量内容并准确反映何时有内容可供下载的应用程序,与需要很长时间才能下载其内容或声称内容可用但随后执行的应用程序相比,更有可能在未来获得执行时间不要下载任何东西。

有关更多信息,请参阅 Apple 关于后台执行的文档:

https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html

【讨论】:

有趣,注册后台获取只是为了计算和安排未来通知的好主意。【参考方案3】:

有三种方法可以做到这一点:

    使用推送通知而不是本地通知并将逻辑移至服务器。问题 - 用户在离线时不会收到通知。

    继续使用本地通知。您必须为每个祈祷时间计划一个新的通知。当然,本地通知的数量是有限的(最多 64 预定通知),但对于一周的通知来说应该足够了。通知不是警报,用户应该打开应用程序以响应收到通知。这样,您始终可以在重新打开应用程序时重新安排所有通知。此外,最后一条通知可能类似于“您有一段时间没有打开应用程序,您将不会收到更多通知”。

    不要创建本地通知,而是在您的设备日历中创建警报/提醒 (Event Kit)

【讨论】:

这些不是一个好方法。祈祷时间应用程序应该在线/离线工作。该应用程序应该能够计算下一个即将到来的祈祷,然后安排通知。我认为,@Carlos 的解决方案最适合。【参考方案4】:

到目前为止,我发现的最佳方法是安排未来 12 天的祈祷(12 天 * 5 次通知 = 60 次通知)。

请注意,iOS 不允许为每个用户安排超过 64 个通知 应用程序。

用户打开应用后,我会删除所有剩余的通知,并在接下来的 12 天内重新安排新的通知。

重要的是,将Background Fetch(工作)添加到您的应用程序中。在 AppDelegate 类中添加以下代码:

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) 
    // Should schedule new notifications from background
    PrayerTimeHelper().scheduleNotifications()
    completionHandler(.newData)

像这样修改 didFinishLaunchingWithOptions 方法:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool 
// Setup Fetch Interval
//UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
    UIApplication.shared.setMinimumBackgroundFetchInterval(12 * 3600) // launch each 12 hours

以下是安排 12 天通知的方法:

/// Schedule notifications for the next coming 12 days.
/// This method is also called by Background Fetch Job
func scheduleNotifications() 
    DispatchQueue.global(qos: .background).async 

        DispatchQueue.main.async 
            self.removeAllPendingAndDeliveredNotifications()

            // create notifications for the next coming 12 days
            for index in 0..<12 
                let newDate = Calendar.current.date(byAdding: .day, value: index, to: Date())!
                let prayers = self.getPrayerDatetime(forDate: newDate)

                // create notification for each prayer
                for iterator in 0..<prayers.count 
                    // Skip sunrise
                    if iterator == 1  continue 

                    // Skip the passed dates
                    let calendar = Calendar.current
                    let components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: prayers[iterator])

                    self.scheduleNotificationFor(prayerId: iterator, prayerTime: components, request: "\(index)\(iterator)")
                

            
        

    


/// Schedule a notification for a specific prayer
@objc private func scheduleNotificationFor(prayerId: Int, prayerTime: DateComponents, request: String) 
    let notifContent = UNMutableNotificationContent()

    // create the title
    let title = NSLocalizedString("app_title", comment: "Prayer Times")
    // create the prayer name
    let prayerName = NSLocalizedString("prayer_" + String(prayerId), comment: "Prayer")

    // set notification items
    notifContent.title = title
    notifContent.body = String.localizedStringWithFormat(NSLocalizedString("time_to_pray", comment: ""), prayerName)
    notifContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "adhan.mp3"))

    let notifTrigger = UNCalendarNotificationTrigger(dateMatching: prayerTime, repeats: false)
    let notifRequest = UNNotificationRequest(identifier: title + request, content: notifContent, trigger: notifTrigger)

    UNUserNotificationCenter.current().add(notifRequest, withCompletionHandler: nil)


/// This removes all current notifications before creating the new ones
func removeAllPendingAndDeliveredNotifications() 
    UNUserNotificationCenter.current().removeAllDeliveredNotifications()
    UNUserNotificationCenter.current().removeAllPendingNotificationRequests()

这适用于我的 Prayer Times 应用程序。

我希望这会有所帮助;)

【讨论】:

以上是关于如何每天在不同时间重复本地通知的主要内容,如果未能解决你的问题,请参考以下文章

Swift:在每天设定的时间之间创建重复的本地通知

每天在不同时间触发本地通知

如何在swift 3中每天在特定时间触发本地通知

如何在本地安排 React 中的重复网络通知?

显示每天 iphone sdk 选定时间的本地通知

安排本地通知从明天开始每天重复