Retired Document
Important: This document may not represent best practices for current development. Links to downloads and other resources may no longer be valid.
CloudPhotos (iOS)/CloudPhotos/AppDelegate.m
/* |
Copyright (C) 2017 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
This sample's application delegate for push notiications (CKSubscriptions) from CloudKit. |
*/ |
@import UserNotifications; |
#import "AppDelegate.h" |
#import "APLMainTableViewController.h" |
#import "APLCloudManager.h" |
#import "CloudPhoto.h" |
@interface AppDelegate () <APLCloudManagerDelegate> |
@end |
@implementation AppDelegate |
// The app delegate must implement the window @property |
// from UIApplicationDelegate @protocol to use a main storyboard file. |
// |
@synthesize window; |
#pragma mark - Application Life Cycle |
- (id)init |
{ |
self = [super init]; |
if (self != nil) |
{ |
// for the sake of UIStateRestoration (we restore early in the launch process) |
// and |
// APLMainTableViewController may be loaded before we have a chance to setup our CloudManager object so create it earlier here |
// |
CloudManager.delegate = self; // we want to be notified by our CloudManager when the user logs in or out |
} |
return self; |
} |
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions |
{ |
// Register for push notifications (from CloudKit) |
// Necessary to receive local or remove notifications. |
// |
// Note: This will not work in the simulator. |
// |
// Note: Without registering, sound and badging won't show up for your app in: Settings -> Notifications -> <this app>, |
// and you won't have permission to badge or show alerts. |
// |
// User permissions can later be adjusted in Settings -> Notifications -> <this app> |
// |
// To reset the Push Notifications Permissions Alert on iOS: |
// 1. Delete your app from the device. |
// 2. Turn the device off completely and turn it back on. |
// 3. Go to Settings > General > Date & Time: and set the date ahead a day or more. |
// 4. Turn the device off completely again and turn it back on. |
// 5. You can then set date and time setting to automatic. |
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; |
[center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) { |
if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) |
{ |
// start the request for authorization |
[center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge) |
completionHandler:^(BOOL granted, NSError * _Nullable error) { |
if (error != nil) |
{ |
NSLog(@"ERROR in request authorization = %@", error); |
} |
else if (granted) |
{ |
// we are good to receive user notifications |
} |
}]; |
} |
else if (settings.authorizationStatus == UNAuthorizationStatusAuthorized) |
{ |
// we are good to receive notifications |
} |
else if (settings.authorizationStatus == UNAuthorizationStatusDenied) |
{ |
// notications are disabled for this app |
} |
}]; |
[application registerForRemoteNotifications]; |
return YES; |
} |
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken |
{ |
//NSLog(@"CloudPhotos: Registered for Push notifications with token: %@\n", deviceToken); |
} |
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error |
{ |
//NSLog(@"CloudPhotos: Push subscription failed\n%@", error); |
} |
#pragma mark - Accessors |
- (APLMainTableViewController *)mainViewController |
{ |
UINavigationController *rootVC = (UINavigationController *)self.window.rootViewController; |
return (APLMainTableViewController *)rootVC.viewControllers[0]; |
} |
#pragma mark - Push Notifications |
// attempt to find the owner of that recordID from this CKQueryNotification and report it (for debugging purposes) |
// |
- (void)reportUserFromNotification:(CKQueryNotification *)queryNotification |
{ |
CKRecordID *recordID = [queryNotification recordID]; |
// note you can't create a CKRecord from a CKQueryNotification, so we need to do a lookup instead |
[CloudManager fetchRecordWithID:recordID completionHandler:^(CKRecord *foundRecord, NSError *error) { |
if (foundRecord != nil) |
{ |
// find out the user who affected this record |
CloudPhoto *pertainingPhoto = [[CloudPhoto alloc] initWithRecord:foundRecord]; |
[pertainingPhoto photoOwner:^(NSString *owner) { |
// we are only interested in the kPhotoTitle attribute |
// (because we set the CKSubscription's CKNotificationInfo 'desiredKeys' when we subscribed earlier) |
NSDictionary *recordFields = [queryNotification recordFields]; |
NSString *photoTitle = recordFields[[APLCloudManager PhotoTitleAttribute]]; |
// here we can examine the title of the photo record without a query |
CKQueryNotificationReason reason = [queryNotification queryNotificationReason]; |
NSString *baseMessage = nil; |
switch (reason) |
{ |
case CKQueryNotificationReasonRecordCreated: |
baseMessage = NSLocalizedString(@"Photo Added Notif Message", nil); |
break; |
case CKQueryNotificationReasonRecordUpdated: |
baseMessage = NSLocalizedString(@"Photo Changed Notif Message", nil); |
break; |
case CKQueryNotificationReasonRecordDeleted: |
baseMessage = NSLocalizedString(@"Photo Removed Notif Message", nil); |
break; |
} |
if (baseMessage != nil) |
{ |
NSString *message = [NSString stringWithFormat:baseMessage, photoTitle, owner]; |
NSLog(@"%@", message); |
} |
}]; |
} |
}]; |
} |
// didReceiveRemoteNotification: |
// |
// This is called when: |
// 1) the app is actively running (no push alert will appear) |
// or |
// 2) the app is in the background (push banner will appear, user taps the banner to open our app) |
// |
// To receive background notifications, this has to be turned on in Xcode: |
// Capabilities -> Background Modes: Remote Notifications |
// where this is added to the Info.plist as a result: |
// <key>UIBackgroundModes</key> |
// <array> |
// <string>remote-notification</string> |
// </array> |
// |
// Note #1: |
// The system calls this method when your app is running in the foreground OR background. |
// We have up to 30 seconds of wall-clock time to process the notification and call the specified completion handler block. |
// So we need to start a background task to process any or all notificiations. |
// |
// Note #2: |
// To test of the app was actually relaunched due to the push receive, |
// use Instrument's Activity Monitor to test if it was truly launched (by checking the "running processes" for that device). |
// |
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler |
{ |
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) |
{ |
//NSLog(@"incoming push notification: app is active"); |
} |
else if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) |
{ |
//NSLog(@"incoming push notification: app is the background"); |
} |
// note: userInfo must have this: |
// "content-available" = 1; |
// also: In Info.plist: |
// <key>UIBackgroundModes</key> |
// <array> |
// <string>remote-notification</string> |
// </array> |
// |
// NOTE: |
// The content-available property with a value of 1 lets the remote notification act as a “silent” notification. |
// When a silent notification arrives, iOS wakes up your app in the background so that you can get new data from |
// your server or do background information processing. Users aren’t told about the new or changed information |
// that results from a silent notification, but they can find out about it the next time they open your app. |
// |
// NOTE: |
// If the notification has 1) alert, 2) badge, or 3) soundKey, then CloudKit uses priority 10, otherwise is uses 5. |
// |
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^ { |
[application endBackgroundTask:bgTask]; |
bgTask = UIBackgroundTaskInvalid; |
}]; |
// on a secondary thread process our incoming push notifications |
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ |
// all incoming push notifications are handled inside our CloudManager object, |
// view controllers of this app will be called via NSNotificationCenter to update their UI |
// |
[CloudManager processNotifications]; |
completionHandler(UIBackgroundFetchResultNewData); |
[application endBackgroundTask:bgTask]; |
bgTask = UIBackgroundTaskInvalid; |
}); |
} |
#pragma mark - UIStateRestoration |
- (BOOL)application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder |
{ |
return YES; |
} |
- (BOOL)application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder |
{ |
return YES; |
} |
#pragma mark - APLCloudManagerDelegate |
// the user signed in or out of iCloud so we need to refresh our UI |
- (void)userLoginChanged |
{ |
// notify the main table of the login change, to give it chance to refresh its UI |
APLMainTableViewController *mainTableViewController = [self mainViewController]; |
[mainTableViewController loginUpdate]; |
UINavigationController *rootVC = (UINavigationController *)self.window.rootViewController; |
UIViewController *currentViewController = rootVC.visibleViewController; |
if ([currentViewController isKindOfClass:[APLDetailTableViewController class]]) |
{ |
[(APLDetailTableViewController *)currentViewController loginUpdate]; |
} |
} |
@end |
Copyright © 2017 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2017-03-09