简体   繁体   English

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

[英]How to repeat local notifications every day at different times

我正在做一个祈祷应用程序,使用户能够为祈祷时间设置警报(本地通知),即用户设置应用程序每天通知他Fajr祷告,问题是每个祷告的时间每天都在变化所以应用程序将在周四通知用户公平的时间与星期五的时间不同,我需要每天重复本地通知,但是根据每日祷告时间,请问,有人可以给我一个想法吗?

There are a few possible solutions for this. 有一些可能的解决方案。 It might be safer to use an approach where a limited number of notifications are scheduled at a time, since iOS only keeps the 64 soonest notifications: 使用一次安排有限数量的通知的方法可能更安全,因为iOS仅保留64个最快的通知:

An app can have only a limited number of scheduled notifications; 一个应用程序只能有有限数量的预定通知; the system keeps the soonest-firing 64 notifications (with automatically rescheduled notifications counting as a single notification) and discards the rest. 系统保持最快的64个通知(自动重新安排的通知计为单个通知)并丢弃其余的通知。

Source: UILocalNotification class reference 来源:UILocalNotification类参考

It is also not a good idea to rely on using the UILocalNotification passed into application:didFinishLaunchingWithOptions: , since it is only passed when the user swipes the notification: 依赖于使用传递给application:didFinishLaunchingWithOptions:UILocalNotification也不是一个好主意application:didFinishLaunchingWithOptions: ,因为它仅在用户滑动通知时传递:

Look at the launch options dictionary to determine why your app was launched. 查看启动选项字典以确定启动应用程序的原因。 The application:willFinishLaunchingWithOptions: and application:didFinishLaunchingWithOptions: methods provide a dictionary with keys indicating the reason that your app was launched. 应用程序:willFinishLaunchingWithOptions:和application:didFinishLaunchingWithOptions:方法提供一个字典,其中包含指示应用程序启动原因的键。

The key value for launching in response to a local notification is: UIApplicationLaunchOptionsLocalNotificationKey 响应本地通知而启动的关键值是: UIApplicationLaunchOptionsLocalNotificationKey

Source: UIApplicationDelegate class reference 来源:UIApplicationDelegate类引用

Option 1: schedule one day at a time (Code for this is provided below) 选项1:一次安排一天(下面提供了相关代码)

One way to handle the scheduling of notifications is to present a schedule to the user, where the day's notifications are scheduled at the time of the initial opening of the app. 处理通知计划的一种方法是向用户呈现计划,其中在应用程序的初始打开时安排当天的通知。

Use a CustomNotificationManager class to handle notifications whose times are variable (code provided below). 使用CustomNotificationManager类处理时间可变的通知(下面提供的代码)。 In your AppDelegate, you can delegate to this class the handling of the local notifications, which will either schedule the current day's notifications plus the following day's fixed-time notification, or respond to a prayer notification. 在您的AppDelegate中,您可以委派此课程处理本地通知,该通知将安排当天的通知加上第二天的固定时间通知,或者回复祷告通知。

If the User opens the app in response to a prayer notification, the app can direct the user to an appropriate part of the app. 如果用户响应祷告通知打开应用程序,则应用程序可以将用户定向到应用程序的适当部分。 If the user opens the app in response to the fixed-time notification, the app will schedule that day's local notifications according to the User's date and location. 如果用户打开应用程序以响应固定时间通知,则应用程序将根据用户的日期和位置安排当天的本地通知。

Option 2 (Slightly slimmer approach, but which provides less to the User) 选项2(略微更薄的方法,但对用户提供的更少)

Another approach is to simply use a prayer notification's app launch to schedule the one that immediately follows. 另一种方法是简单地使用祷告通知的应用程序启动来安排紧随其后的那个。 However, this is less reliable, and does not provide the ability to preview a schedule of notifications. 但是,这不太可靠,并且不提供预览通知计划的能力。

Notification Manager Header file 通知管理器头文件

@interface CustomNotificationManager : NSObject

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

@end

Notification Manager Implementation file 通知管理器实施文件

#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 Implementation AppDelegate didReceiveLocalNotification实现

- (void) application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
    CustomNotificationManager *notificationManager = [[CustomNotificationManager alloc] init];
    [notificationManager handleLocalNotification:notification];
}

Suggestion for possible modification: If the CustomNotificationManager needs to maintain state, you could convert it to a Singleton. 建议可能的修改:如果CustomNotificationManager需要维护状态,您可以将其转换为Singleton。

So the problem appears you need to be setting this local notification every now and then, but can't be a repeatable notification. 因此,问题似乎是您需要不时地设置此本地通知,但不能是可重复的通知。 I assume the user sets the prayer time, and wants to be notified. 我假设用户设定了祈祷时间,并希望得到通知。 I suggest you set a few of them, since you know from the list. 我建议你设置一些,因为你从列表中知道。 Then set background fetch for let say every 5 hours, and upon app background launch, just check what local notifications are still set, and update the list accordingly based on the current date. 然后设置后台提取,让我们说每5个小时,并在应用程序后台启动时,只检查仍然设置的本地通知,并根据当前日期相应地更新列表。 Background fetch doesn't wake your app precisely every 5 hours in this case, but will do its best. 在这种情况下,后台提取不会每5小时精确唤醒您的应用程序,但会尽力而为。 I'm sure your app will wake up at least twice a day. 我相信你的应用程序每天至少会醒来两次。 You can tweak the time based on your needs. 您可以根据需要调整时间。

Fetching Small Amounts of Content Opportunistically Apps that need to check for new content periodically can ask the system to wake them up so that they can initiate a fetch operation for that content. 有机地获取少量内容需要定期检查新内容的应用程序可以要求系统将其唤醒,以便它们可以启动对该内容的获取操作。 To support this mode, enable the Background fetch option from the Background modes section of the Capabilities tab in your Xcode project. 要支持此模式,请从Xcode项目的“功能”选项卡的“后台模式”部分启用“后台获取”选项。 (You can also enable this support by including the UIBackgroundModes key with the fetch value in your app's Info.plist file.) Enabling this mode is not a guarantee that the system will give your app any time to perform background fetches. (您还可以通过在应用程序的Info.plist文件中包含带有提取值的UIBackgroundModes键来启用此支持。)启用此模式并不能保证系统会随时为您的应用程序执行后台提取。 The system must balance your app's need to fetch content with the needs of other apps and the system itself. 系统必须平衡您的应用程序根据其他应用程序和系统本身的需求获取内容的需求。 After assessing that information, the system gives time to apps when there are good opportunities to do so. 在评估该信息后,系统会在有良好机会的情况下为应用程序提供时间。 When a good opportunity arises, the system wakes or launches your app into the background and calls the app delegate's application:performFetchWithCompletionHandler: method. 当出现好机会时,系统会将应用程序唤醒或启动到后台并调用应用程序委托的应用程序:performFetchWithCompletionHandler:method。 Use that method to check for new content and initiate a download operation if content is available. 如果内容可用,请使用该方法检查新内容并启动下载操作。 As soon as you finish downloading the new content, you must execute the provided completion handler block, passing a result that indicates whether content was available. 完成下载新内容后,必须执行提供的完成处理程序块,并传递指示内容是否可用的结果。 Executing this block tells the system that it can move your app back to the suspended state and evaluate its power usage. 执行此块会告诉系统它可以将您的应用程序移回暂停状态并评估其耗电量。 Apps that download small amounts of content quickly, and accurately reflect when they had content available to download, are more likely to receive execution time in the future than apps that take a long time to download their content or that claim content was available but then do not download anything. 快速下载少量内容并准确反映其内容可供下载的应用程序,比将需要很长时间下载内容或声明内容可用的应用程序更有可能在未来获得执行时间不下载任何东西。

For more information refers to Apple's documentation on background execution: 有关更多信息,请参阅Apple有关后台执行的文档:

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

There are three ways to do this: 有三种方法可以做到这一点:

  1. Use push notifications instead of local notifications and move the logic to the server. 使用推送通知而不是本地通知,并将逻辑移动到服务器。 Problem - user won't get notifications when offline. 问题 - 用户在离线时不会收到通知。

  2. Keep using local notifications. 继续使用本地通知。 You will have to plan a new notification for every prayer time. 您必须为每个祷告时间计划一个新通知。 Of course, the number of local notifications is limited (max 64 scheduled notifications) but it should be enough for a week of notifications. 当然,本地通知的数量是有限的(最多64预定通知),但它应足以通知一周。 A notification is not an alarm, the user is supposed to open the application in response to receiving a notification. 通知不是警报,用户应该响应于接收通知而打开应用程序。 That way, you can always reschedule all notifications when the app is reopened. 这样,您可以在重新打开应用程序时重新安排所有通知。 Also, the last notification can be something similar to "you have not opened the app in a while, you won't be receiving more notifications". 此外,最后一个通知可能类似于“您暂时没有打开应用程序,您将不会收到更多通知”。

  3. Instead of creating local notifications, create alarms/reminders in your device calendar ( Event Kit ) 而不是创建本地通知,在设备日历中创建警报/提醒( 事件工具包

The best way I found, so far, is to schedule the prayers for the next coming 12 days (12 days * 5 notifications = 60 notifications). 到目前为止,我找到的最好方法是为接下来的12天(12天* 5次通知= 60次通知)安排祈祷。

Note that iOS doesn't allow to schedule more than 64 notifications per app. 请注意,iOS不允许为每个应用安排超过64个通知。

Once the user open the app, I remove all remaining notifications and re-schedule a new ones for the next 12 days. 用户打开应用后,我会删除所有剩余的通知,并在接下来的12天内重新安排新的通知。

The important thing the do, is to add a Background Fetch (job) to your application. 重要的是,为您的应用程序添加后台提取 (作业)。 In AppDelegate class add this code: AppDelegate类中添加以下代码:

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

Modify didFinishLaunchingWithOptions method like this: 像这样修改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
}

Here is the methods that schedule the 12 days notifications: 以下是安排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()
}

This is working fine for my Prayer Times app. 这对我的祈祷时间应用程序工作正常。

I hope this will help ;) 我希望这个能帮上忙 ;)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM