Skip to content

Instantly share code, notes, and snippets.

@colbylwilliams
Created November 30, 2015 15:31
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save colbylwilliams/33251a77ceb64c7cbc86 to your computer and use it in GitHub Desktop.
Save colbylwilliams/33251a77ceb64c7cbc86 to your computer and use it in GitHub Desktop.
using System;
using UIKit;
using Foundation;
using System.Collections.Generic;
namespace Notifications.iOS
{
#region APN Payload Container
/* *
* List of the keys and expected values of the aps payload.
* --
*
* alert
* - string or dictionary
* - If this property is included, the system displays a standard alert. You may specify a
* string as the value of alert or a dictionary as its value. If you specify a string,
* it becomes the message text of an alert with two buttons: Close and View. If the user
* taps View, the app is launched.
* Alternatively, you can specify a dictionary as the value of alert.
*
* badge
* - number
* - The number to display as the badge of the app icon.
* If this property is absent, the badge is not changed. To remove the badge, set the
* value of this property to 0.
*
* sound
* - string
* - The name of a sound file in the app bundle or in the Library/Sounds folder of the app’s
* data container. The sound in this file is played as an alert. If the sound file doesn’t
* exist or default is specified as the value, the default alert sound is played. The
* audio must be in one of the audio data formats that are compatible with system sounds;
* see Preparing Custom Alert Sounds for details.
*
* content-available
* - number
* - Provide this key with a value of 1 to indicate that new content is available. Including
* this key and value means that when your app is launched in the background or resumed,
* application:didReceiveRemoteNotification:fetchCompletionHandler: is called.
*
* category
* - string
* - Provide this key with a string value that represents the identifier property of the
* UIMutableUserNotificationCategory object you created to define custom actions.
*
*
*
* * Keys and expected values for the alert dictionary include:
* * --
* *
* * title
* * - string
* * - A short string describing the purpose of the notification. Apple Watch displays this
* * string as part of the notification interface. This string is displayed only briefly and
* * should be crafted so that it can be understood quickly. This key was added in iOS 8.2.
* *
* * body
* * - string
* * - The text of the alert message.
* *
* * title-loc-key
* * - string or null
* * - The key to a title string in the Localizable.strings file for the current localization.
* * The key string can be formatted with %@ and %n$@ specifiers to take the variables
* * specified in the title-loc-args array. This key was added in iOS 8.2.
* *
* * title-loc-args
* * - array of strings or null
* * - Variable string values to appear in place of the format specifiers in title-loc-key.
* * See Localized Formatted Strings for more information. This key was added in iOS 8.2.
* *
* * action-loc-key
* * - string or null
* * - If a string is specified, the system displays an alert that includes the Close and View
* * buttons. The string is used as a key to get a localized string in the current
* * localization to use for the right button’s title instead of “View”.
* *
* * loc-key
* * - string
* * - A key to an alert-message string in a Localizable.strings file for the current
* * localization (which is set by the user’s language preference). The key string can be
* * formatted with %@ and %n$@ specifiers to take the variables specified in the loc-args
* * array.
* *
* * loc-args
* * - array of strings
* * - Variable string values to appear in place of the format specifiers in loc-key.
* *
* * launch-image
* * - string
* * - The filename of an image file in the app bundle; it may include the extension or omit
* * it. The image is used as the launch image when users tap the action button or move the
* * action slider. If this property is not specified, the system either uses the previous
* * snapshot,uses the image identified by the UILaunchImageFile key in the app’s Info.plist
* * file, or falls back to Default.png. This property was added in iOS 4.0.
* *
* */
public class ApnPayload
{
public string alertString { get; set; }
public ApnPayloadAlert alertDict { get; set; }
public int badge { get; set; }
public string sound { get; set; }
public bool content_available { get; set; }
public string category { get; set; }
public ApnPayload (NSDictionary payload)
{
alertString = payload.ValueForKey(ApnPayloadKeys.alert)?.ToString();
alertDict = new ApnPayloadAlert (payload.ValueForKey(ApnPayloadKeys.alert) as NSDictionary);
badge = Convert.ToInt32(payload.ValueForKey(ApnPayloadKeys.badge) as NSNumber);
sound = payload.ValueForKey(ApnPayloadKeys.sound)?.ToString();
content_available = Convert.ToInt32(payload.ValueForKey(ApnPayloadKeys.content_available) as NSNumber) == 1;
category = payload.ValueForKey(ApnPayloadKeys.category)?.ToString();
}
}
public class ApnPayloadAlert
{
public string body { get; set; }
public string show_view { get; set; }
public string title { get; set; }
public List<string> title_loc_key { get; set; }
public string action_loc_key { get; set; }
public string loc_key { get; set; }
public List<string> loc_args { get; set; }
public string launch_image { get; set; }
public ApnPayloadAlert (NSDictionary alertDict)
{
if (alertDict != null) {
body = alertDict.ValueForKey(ApnPayloadKeys.Alert.body)?.ToString();
show_view = alertDict.ValueForKey(ApnPayloadKeys.Alert.show_view)?.ToString();
title = alertDict.ValueForKey(ApnPayloadKeys.Alert.title)?.ToString();
action_loc_key = alertDict.ValueForKey(ApnPayloadKeys.Alert.action_loc_key)?.ToString();
loc_key = alertDict.ValueForKey(ApnPayloadKeys.Alert.loc_key)?.ToString();
launch_image = alertDict.ValueForKey(ApnPayloadKeys.Alert.launch_image)?.ToString();
title_loc_key = new List<string> ();
var title_loc_key_arr = alertDict.ValueForKey(ApnPayloadKeys.Alert.title_loc_key) as NSArray;
for (nuint i = 0; i < title_loc_key_arr.Count; i++) {
title_loc_key.Add(title_loc_key_arr.GetItem<NSString>(i)?.ToString());
}
loc_args = new List<string> ();
var loc_args_arr = alertDict.ValueForKey(ApnPayloadKeys.Alert.loc_args) as NSArray;
for (nuint i = 0; i < loc_args_arr.Count; i++) {
loc_args.Add(loc_args_arr.GetItem<NSString>(i)?.ToString());
}
}
}
}
#endregion
#region APN Payload Container
public static class ApnPayloadKeys
{
public static NSString payload = new NSString ("aps");
public static NSString alert = new NSString ("alert");
public static NSString badge = new NSString ("badge");
public static NSString sound = new NSString ("sound");
public static NSString content_available = new NSString ("content-available");
public static NSString category = new NSString ("category");
public static class Alert
{
public static NSString body = new NSString ("body");
public static NSString show_view = new NSString ("show-view");
public static NSString title = new NSString ("title");
public static NSString title_loc_key = new NSString ("title-loc-key");
public static NSString action_loc_key = new NSString ("action-loc-key");
public static NSString loc_key = new NSString ("loc-key");
public static NSString loc_args = new NSString ("loc-args");
public static NSString launch_image = new NSString ("launch-image");
}
}
#endregion
public class NotificationAppDelegate : UIApplicationDelegate
{
nint BackgroundTaskId;
static readonly bool iOS8 = UIDevice.CurrentDevice.CheckSystemVersion(8, 0);
public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
{
/* Wrap everything in a try/catch, otherwise when the app receives a "silent" notification
* and is launched into the background, then crashes immediately, you'll never know, as
* it crashes before DidReceiveRemoteNotification can be called.
* This happens often when the user is doing something with the UI here without checking
* whether or the app was launched into the background
* */
try {
if (launchOptions != null) {
// check for a local notification
if (launchOptions.ContainsKey(UIApplication.LaunchOptionsLocalNotificationKey)) {
var localNotification = launchOptions[UIApplication.LaunchOptionsLocalNotificationKey] as UILocalNotification;
if (localNotification != null) {
Console.WriteLine(localNotification);
}
// reset our badge
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
}
// check for a remote notification (DidReceiveRemoteNotification will still get called)
if (launchOptions.ContainsKey(UIApplication.LaunchOptionsRemoteNotificationKey)) {
var remoteNotification = launchOptions[UIApplication.LaunchOptionsRemoteNotificationKey] as NSDictionary;
if (remoteNotification != null) {
var notificationPayload = new ApnPayload (remoteNotification);
Console.WriteLine(notificationPayload);
// reset our badge
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
}
}
}
registerForRemoteNotifications();
// application.SetMinimumBackgroundFetchInterval(UIApplication.BackgroundFetchIntervalMinimum);
} catch (Exception ex) {
Console.WriteLine(ex.Message);
throw ex;
}
return true;
}
#region Register for Notifications
static void registerForRemoteNotifications ()
{
if (iOS8) {
/* The notification types represent the user interface elements the app displays when
* it receives a notification: badging the app’s icon, playing a sound, and displaying
* an alert. If you don’t register any notification types, the system pushes all remote
* notifications to your app silently, that is, without displaying any user interface.
* */
var notificationSettings = UIUserNotificationSettings.GetSettingsForTypes(UIUserNotificationType.Sound | UIUserNotificationType.Alert | UIUserNotificationType.Badge, null);
UIApplication.SharedApplication.RegisterUserNotificationSettings(notificationSettings);
UIApplication.SharedApplication.RegisterForRemoteNotifications();
} else {
UIApplication.SharedApplication.RegisterForRemoteNotificationTypes(UIRemoteNotificationType.Alert | UIRemoteNotificationType.Badge);
}
}
// Successfully registered with Apple Push Notification service (APNs).
public override void RegisteredForRemoteNotifications (UIApplication application, NSData deviceToken)
{
/* application
* The app object that initiated the remote-notification registration process.
*
* deviceToken
* A token that identifies the device to APNs. The token is an opaque data
* type because that is the form that the provider needs to submit to the APNs servers
* when it sends a notification to a device. The APNs servers require a binary format
* for performance reasons.
* The size of a device token is 32 bytes.
* Note that the device token is different from the uniqueIdentifier property of UIDevice
* because, for security and privacy reasons, it must change when the device is wiped.
* */
var token = deviceToken.ToString();
Console.WriteLine("Successfully registered for Remote Notifications :\n{0}", token);
/* Send this token to your server along with any other information about the user you want
* to be associated with this device. When you want to send a push notification to this
* user, you'll use this token to identify them.
* */
}
// Failed to successfully register with Apple Push Notification service (APNs).
public override void FailedToRegisterForRemoteNotifications (UIApplication application, NSError error)
{
/* application
* The app object that initiated the remote-notification registration process.
*
* error
* An NSError object that encapsulates information why registration did not succeed.
* The app can choose to display this information to the user.
* Process the error object appropriately and make sure you disable any logic within the
* app that depends on receiving remote notifications. You don't want do any unnecessary
* processing within the app for notifications that aren't going to be coming in. Just
* gracefully degrade.
* */
Console.WriteLine("Error registering for Remote Notifications :\n{0}", error.LocalizedDescription);
}
#endregion
#region Handle Notifications
// Currently running app receives a local notification.
public override void ReceivedLocalNotification (UIApplication application, UILocalNotification notification)
{
/* application
* The app object that received the local notification.
*
* notification
* A local notification that encapsulates details about the notification, potentially
* including custom data.
*
* completionHandler
* The block to execute when the download operation is complete. When calling this block,
* pass in the fetch result value that best describes the results of your download
* operation. You must call this handler and should do so as soon as possible. For a list
* of possible values, see the UIBackgroundFetchResult type.
*
*
* Discussion
*
* As soon as you finish processing the notification, you must call the block in the
* handler parameter or your app will be terminated. Your app has up to 30 seconds of
* wall-clock time to process the notification and call the specified completion handler
* block. In practice, you should call the handler block as soon as you are done
* processing the notification.
*
* The system tracks the elapsed time, power usage, and
* data costs for your app’s background downloads. Apps that use significant amounts of
* power when processing remote notifications may not always be woken up early to process
* future notifications.
* */
if (notification != null) {
Console.WriteLine(notification);
}
// reset our badge
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
}
// Implement DidReceiveRemoteNotification method instead of this one whenever possible. If
// the delegate implements both methods, the app object calls DidReceiveRemoteNotification
public override void ReceivedRemoteNotification (UIApplication application, NSDictionary userInfo)
{
/* application
* Your singleton app object.
*
* userInfo
* A dictionary that contains information related to the remote notification, potentially
* including a badge number for the app icon, an alert sound, an alert message to display
* to the user, a notification identifier, and custom data. The provider originates it as
* a JSON-defined dictionary that iOS converts to an NSDictionary object; the dictionary
* may contain only property-list objects plus NSNull.
*
* */
}
// A remote notification arrived that indicates there is data to be fetched.
public override void DidReceiveRemoteNotification (UIApplication application, NSDictionary userInfo, Action<UIBackgroundFetchResult> completionHandler)
{
/* application
* Your singleton app object.
*
* userInfo
* A dictionary that contains information related to the remote notification, potentially
* including a badge number for the app icon, an alert sound, an alert message to display
* to the user, a notification identifier, and custom data. The provider originates it as
* a JSON-defined dictionary that iOS converts to an NSDictionary object; the dictionary
* may contain only property-list objects plus NSNull.
*
* completionHandler
* The block to execute when the download operation is complete. When calling this block,
* pass in the fetch result value that best describes the results of your download
* operation. You must call this handler and should do so as soon as possible. For a list
* of possible values, see the UIBackgroundFetchResult type.
*
*
* Discussion
*
* Use this method to process incoming remote notifications for your app. Unlike the
* ReceivedRemoteNotification method, which is called only when your app is running in
* the foreground, the system calls this method when your app is running in the foreground
* or background. In addition, if you enabled the remote notifications background mode,
* the system launches your app (or wakes it from the suspended state) and puts it in the
* background state when a remote notification arrives. However, the system does not
* automatically launch your app if the user has force-quit it. In that situation, the
* user must relaunch your app or restart the device before the system attempts to launch
* your app automatically again.
*
* | NOTE: If the user opens your app from the system-displayed alert, the system may
* | call this method again when your app is about to enter the foreground so that you
* | can update your user interface and display information pertaining to the notification.
*
* When a remote notification arrives, the system displays the notification to the user
* and launches the app in the background (if needed) so that it can call this method.
* Launching your app in the background gives you time to process the notification and
* download any data associated with it, minimizing the amount of time that elapses
* between the arrival of the notification and displaying that data to the user.
*
* As soon as you finish processing the notification, you must call the block in the
* handler parameter or your app will be terminated. Your app has up to 30 seconds of
* wall-clock time to process the notification and call the specified completion handler
* block. In practice, you should call the handler block as soon as you are done
* processing the notification.
*
* The system tracks the elapsed time, power usage, and
* data costs for your app’s background downloads. Apps that use significant amounts of
* power when processing remote notifications may not always be woken up early to process
* future notifications.
* */
try {
switch (application.ApplicationState) {
case UIApplicationState.Active:
break;
case UIApplicationState.Inactive:
break;
case UIApplicationState.Background:
break;
}
// check for a remote notification (DidReceiveRemoteNotification will still get called)
if (userInfo.ContainsKey(UIApplication.LaunchOptionsRemoteNotificationKey)) {
var remoteNotification = userInfo[UIApplication.LaunchOptionsRemoteNotificationKey] as NSDictionary;
if (remoteNotification != null) {
var notificationPayload = new ApnPayload (remoteNotification);
Console.WriteLine(notificationPayload);
// reset our badge
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
}
}
Console.WriteLine("Perform whatever background fetching you need to do");
var dataToFetch = true;
if (dataToFetch) {
// await fetch data
completionHandler(UIBackgroundFetchResult.NewData);
} else {
completionHandler(UIBackgroundFetchResult.NoData);
}
} catch (Exception ex) {
Console.WriteLine(ex.Message);
// you *must* call this at some point
completionHandler(UIBackgroundFetchResult.Failed);
}
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment