Dynamic notification action buttons in Xamarin.iOS
In iOS 12, notifications can dynamically add, remove, and update their associated action buttons. Such customization makes it possible to provide users with actions that are directly relevant to the notification's content and the user's interaction with it.
Sample app: RedGreenNotifications
The code snippets in this guide come from a sample app, which demonstrates how to use Xamarin.iOS to work with notification action buttons in iOS 12.
This sample app sends two types of local notifications: red and green. After having the app send a notification, use 3D Touch to view its custom user interface. Then, use the notification's action buttons to rotate the image it displays. As the image rotates, a Reset rotation button appears and disappears as necessary.
Code snippets in this guide come from this sample app.
Default action buttons
A notification's category determines its default action buttons.
Create and register notification categories while an application launches.
For example, in the sample app, the
FinishedLaunching
method of AppDelegate
does the following:
- Defines one category for red notifications and another for green notifications
- Registers these categories by calling the
SetNotificationCategories
method ofUNUserNotificationCenter
- Attaches a single
UNNotificationAction
to each category
The following sample code shows how this works:
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
// Request authorization to send notifications
UNUserNotificationCenter center = UNUserNotificationCenter.Current;
var options = UNAuthorizationOptions.Alert | UNAuthorizationOptions.Sound | UNAuthorizationOptions.Provisional | UNAuthorizationOptions.ProvidesAppNotificationSettings;
center.RequestAuthorization(options, (bool success, NSError error) =>
{
// ...
var rotateTwentyDegreesAction = UNNotificationAction.FromIdentifier("rotate-twenty-degrees-action", "Rotate 20°", UNNotificationActionOptions.None);
var redCategory = UNNotificationCategory.FromIdentifier(
"red-category",
new UNNotificationAction[] { rotateTwentyDegreesAction },
new string[] { },
UNNotificationCategoryOptions.CustomDismissAction
);
var greenCategory = UNNotificationCategory.FromIdentifier(
"green-category",
new UNNotificationAction[] { rotateTwentyDegreesAction },
new string[] { },
UNNotificationCategoryOptions.CustomDismissAction
);
var set = new NSSet<UNNotificationCategory>(redCategory, greenCategory);
center.SetNotificationCategories(set);
});
// ...
}
Based on this code, any notification whose
Content.CategoryIdentifier
is "red-category" or "green-category" will, by default, show a
Rotate 20° action button.
In-app handling of notification action buttons
UNUserNotificationCenter
has a Delegate
property of type
IUNUserNotificationCenterDelegate
.
In the sample app, AppDelegate
sets itself as the user notification
center's delegate in FinishedLaunching
:
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
// Request authorization to send notifications
UNUserNotificationCenter center = UNUserNotificationCenter.Current;
var options = // ...
center.RequestAuthorization(options, (bool success, NSError error) =>
{
center.Delegate = this;
// ...
Then, AppDelegate
implements
DidReceiveNotificationResponse
to handle action button taps:
[Export("userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:")]
public void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, System.Action completionHandler)
{
if (response.IsDefaultAction)
{
Console.WriteLine("ACTION: Default");
}
if (response.IsDismissAction)
{
Console.WriteLine("ACTION: Dismiss");
}
else
{
Console.WriteLine($"ACTION: {response.ActionIdentifier}");
}
completionHandler();
}
This implementation of DidReceiveNotificationResponse
does not
handle the notification's Rotate 20° action button. Instead, the
notification's content extension handles taps on this button. The next
section further discusses notification action button handling.
Action buttons in the notification content extension
A notification content extension contains a view controller that defines the custom interface for a notification.
This view controller can use the GetNotificationActions
and
SetNotificationActions
methods on its
ExtensionContext
property to access and modify the notification's action buttons.
In the sample app, the notification content extension's view controller modifies the action buttons only when responding to a tap on an already-existing action button.
Note
A notification content extension can respond to an action button tap in
its view controller's DidReceiveNotificationResponse
method, declared as part of
IUNNotificationContentExtension.
Though it shares a name with the DidReceiveNotificationResponse
method
described above, this
is a different method.
After a notification content extension finishes processing a button tap, it can choose whether or not to tell the main application to handle that same button tap. To do this, it must pass an appropriate value of UNNotificationContentExtensionResponseOption to its completion handler:
Dismiss
indicates that the notification interface should be dismissed, and that the main app does not need to handle the button tap.DismissAndForwardAction
indicates that the notification interface should be dismissed, and that the main app should also handle the button tap.DoNotDismiss
indicates that the notification interface should not be dismissed, and that the main app does not need to handle the button tap.
The content extension's DidReceiveNotificationResponse
method determines
which action button was tapped, rotates the image in the notification's
interface, and shows or hides a Reset action button:
[Export("didReceiveNotificationResponse:completionHandler:")]
public void DidReceiveNotificationResponse(UNNotificationResponse response, Action<UNNotificationContentExtensionResponseOption> completionHandler)
{
var rotationAction = ExtensionContext.GetNotificationActions()[0];
if (response.ActionIdentifier == "rotate-twenty-degrees-action")
{
rotationButtonTaps += 1;
double radians = (20 * rotationButtonTaps) * (2 * Math.PI / 360.0);
Xamagon.Transform = CGAffineTransform.MakeRotation((float)radians);
// 9 rotations * 20 degrees = 180 degrees. No reason to
// show the reset rotation button when the image is half
// or fully rotated.
if (rotationButtonTaps % 9 == 0)
{
ExtensionContext.SetNotificationActions(new UNNotificationAction[] { rotationAction });
}
else if (rotationButtonTaps % 9 == 1)
{
var resetRotationAction = UNNotificationAction.FromIdentifier("reset-rotation-action", "Reset rotation", UNNotificationActionOptions.None);
ExtensionContext.SetNotificationActions(new UNNotificationAction[] { rotationAction, resetRotationAction });
}
}
if (response.ActionIdentifier == "reset-rotation-action")
{
rotationButtonTaps = 0;
double radians = (20 * rotationButtonTaps) * (2 * Math.PI / 360.0);
Xamagon.Transform = CGAffineTransform.MakeRotation((float)radians);
ExtensionContext.SetNotificationActions(new UNNotificationAction[] { rotationAction });
}
completionHandler(UNNotificationContentExtensionResponseOption.DoNotDismiss);
}
In this case, the method passes
UNNotificationContentExtensionResponseOption.DoNotDismiss
to its
completion handler. This which means that the notification's interface will
stay open.