How to repeat local notifications every day at different times

前端 未结 4 546
名媛妹妹
名媛妹妹 2020-12-28 09:45

I\'m working on a prayers application that enables users to set an alarm(local notification) for prayer times, i.e. the user sets the application to notify him for Fajr pray

相关标签:
4条回答
  • 2020-12-28 10:27

    The best way I found, so far, is to schedule the prayers for the next coming 12 days (12 days * 5 notifications = 60 notifications).

    Note that iOS doesn't allow to schedule more than 64 notifications per app.

    Once the user open the app, I remove all remaining notifications and re-schedule a new ones for the next 12 days.

    The important thing the do, is to add a Background Fetch (job) to your application. In AppDelegate class add this code:

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

    Modify didFinishLaunchingWithOptions method like this:

    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:

    /// 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 ;)

    0 讨论(0)
  • 2020-12-28 10:30

    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. Background fetch doesn't wake your app precisely every 5 hours in this case, but will do its best. 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. (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. 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. 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:

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

    0 讨论(0)
  • 2020-12-28 10:38

    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:

    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.

    Source: UILocalNotification class reference

    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:

    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.

    The key value for launching in response to a local notification is: UIApplicationLaunchOptionsLocalNotificationKey

    Source: UIApplicationDelegate class reference

    Option 1: schedule one day at a time (Code for this is provided below)

    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). 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.

    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)

    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

    - (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.

    0 讨论(0)
  • 2020-12-28 10:44

    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. 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)

    0 讨论(0)
提交回复
热议问题