iOS Message Center
The Message Center is a user-addressable, rich message listing. It requires little to no integration to get started and supports some basic theming options.
This guide covers the various tools available for creating custom Message Center implementations, starting with the simplest and moving on to more advanced use cases.
AirshipMessageCenter
Message center is available in the MessageCenter module and subspec.
Importing the module
For CocoaPods, import using:
import AirshipKit
@import AirshipKit;
For everything else, use:
import AirshipMessageCenter
@import AirshipMessageCenter;
Adding a Custom Look and Feel
The default implementation displays with default settings for elements such as colors, fonts, and title text. In the following section we’ll explore ways to customize its look and feel without having to create custom UI.
Most developers will want to customize the look and feel to match their app’s existing style and layout. By setting a handful of top-level properties for the Message Center you can override the default style to quickly arrive at a look and feel matching that of your app. We’ll cover a few of these to show how easy this process can be.
Title
A good place to start is the title
property on
UAMessageCenter
. This is the title text that
displays in the navigation bar above the message list. By default, the
title is a localized string reading “Message Center”, but it can be
overridden by setting the property directly.
MessageCenter.shared.defaultUI.title = "My Message Center"
UAMessageCenter.shared.defaultUI.title = @"My Message Center";
Styling the Message Center
The message center’s look can be customized by creating a UAMessageCenterStyle instance, setting its style properties, and then setting the style property of the Message Center instance to the customized style instance.
For instance, we may want to change the color of the navigation bar in order to match our app and set custom fonts for the title and table view cells. In the example below, we assume the app has access to the “Roboto” font, but any stock or custom font may be used.
let style = MessageCenterStyle()
// Customize the style object
style.navigationBarColor = UIColor(red: 0.988, green: 0.694, blue: 0.106, alpha: 1)
style.titleColor = UIColor(red: 0.039, green: 0.341, blue: 0.490, alpha: 1)
style.tintColor = UIColor(red: 0.039, green: 0.341, blue: 0.490, alpha: 1)
style.titleFont = UIFont(name: "Roboto-Regular", size: 17.0)
style.cellTitleFont = UIFont(name: "Roboto-Bold", size: 14.0)
style.cellDateFont = UIFont(name: "Roboto-Light", size: 12.0)
// Set the style on the default Message Center UI
MessageCenter.shared.defaultUI.style = style
UAMessageCenterStyle *style = [UAMessageCenterStyle style];
// Customize the style object
style.navigationBarColor = [UIColor colorWithRed:0.988 green:0.694 blue:0.106 alpha:1];
style.titleColor = [UIColor colorWithRed:0.039 green:0.341 blue:0.490 alpha:1];
style.tintColor = [UIColor colorWithRed:0.039 green:0.341 blue:0.490 alpha:1];
style.titleFont = [UIFont fontWithName:@"Roboto-Regular" size:17.0];
style.cellTitleFont = [UIFont fontWithName:@"Roboto-Bold" size:13.0];
style.cellDateFont = [UIFont fontWithName:@"Roboto-Light" size:12.0];
// Set the style on the default Message Center UI
UAMessageCenter.shared.defaultUI.style = style;
This can also be done without writing code by creating a plist file. Create a plist with the desired message center style. All the keys correspond to properties on the UAMessageCenterStyle class.
Colors are represented by strings, either a valid color hexadecimal or a named color. Named color strings must correspond to a named color defined in a color asset within the main bundle.
Save the plist and include it in the application’s target. Then, update messageCenterStyleConfig with the name of the plist file.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>titleFont</key>
<dict>
<key>fontName</key>
<string>Roboto-Regular</string>
<key>fontSize</key>
<string>17</string>
</dict>
<key>titleColor</key>
<string>#00698f</string>
<key>tintColor</key>
<string>#00698f</string>
<key>navigationBarColor</key>
<string>myNavBarColor</string>
<key>iconsEnabled</key>
<false/>
<key>cellTitleFont</key>
<dict>
<key>fontName</key>
<string>Roboto-Bold</string>
<key>fontSize</key>
<string>13</string>
</dict>
<key>cellDateFont</key>
<dict>
<key>fontName</key>
<string>Roboto-Light</string>
<key>fontSize</key>
<string>17</string>
</dict>
</dict>
</plist>
Message Center Filtering
Sometimes it can be useful to filter the contents of the message center according to some predetermined pattern. To facilitate this, use the UAMessageCenter filter property to set a predicate. Once set, only messages that match the predicate will be displayed. As a simple example, the filter shown keeps only messages whose titles contain the string “Cool”.
MessageCenter.shared.defaultUI.filter = NSPredicate { (evaluatedObject, _) in
let message = evaluatedObject as! UAInboxMessage
let titleString:NSString = message.title as NSString
return titleString.contains("Cool")
};
[UAMessageCenter.shared defaultUI].filter = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
UAInboxMessage *message = (UAInboxMessage *)evaluatedObject;
return [message.title containsString:@"Cool"];
}];
Badge Updates
The value of the badge can be set in a number of ways. Most often, the number corresponds with some notion of unread or unviewed content, e.g., email messages or friend requests. The most common way to update the badge is directly through APNs, either by passing along the badge integer in the payload of a push notification, or by telling the app to adjust the value using our autobadge feature.
For applications implementing the Message Center, you may want the badge value to represent the actual state of unread messages in the Message Center. If so, then you need to set this behavior in the application delegate.
func applicationWillResignActive(application: UIApplication) {
if (MessageCenter.shared.messageList.unreadCount >= 0) {
application.applicationIconBadgeNumber = MessageCenter.shared.messageList.unreadCount
}
}
- (void)applicationWillResignActive:(UIApplication *)application {
if (UAMessageCenter.shared.messageList.unreadCount >= 0) {
application.applicationIconBadgeNumber = UAMessageCenter.shared.messageList.unreadCount;
}
}
If you use this method then you should never include badge values in the payload of push notifications, because Message Center will override them.
App Transport Security
Message Center content features such as Landing Pages are only able to
load SSL-encrypted web content out of the box. This iOS feature, known as App Transport Security,
requires all web connections to use SSL, and only allows for exceptions on
an individual basis. Content hosted on Airship’s servers is already SSL-encrypted, but if you plan
to use your own content, you will need to make sure it is accessible behind SSL so that iOS will not prevent the SDK from
loading it. While we generally recommend leaving these default settings alone, if your app will need to load plain HTTP content
then you can add individual domains in your app’s Info.plist
file. For more information, see Apple’s
App Transport Security Technote .
Advanced Styles
If your app has the ability to send list icons, these can be enabled by setting a single boolean parameter.
style.isIconsEnabled = true
style.iconsEnabled = YES;
Localization
The Message Center uses string resources to localize its UI.
Ordinarily the SDK will read a file named UAMessageCenterUI.strings
in the
AirshipResources
bundle. However, you can override this by adding your own
versions of the file to the main bundle of your app. The SDK will search here
first, falling back on AirshipResources
as necessary.
At present, the strings available for localization are as follows.
"UA_Message_Center_Title" = "Message Center";
"UA_Message" = "Message";
"UA_OK" = "OK";
"UA_Retry" = "Retry";
"UA_Cancel" = "Cancel";
"UA_Loading" = "Updating";
"UA_No_Messages" = "No Messages";
"UA_No_Message_Selected" = "No Message Selected";
"UA_Select_All" = "Select All";
"UA_Select_None" = "Select None";
"UA_Delete" = "Delete";
"UA_Mark_as_Read" = "Mark Read";
"UA_Error_Connection" = "Connection Error";
"UA_Error_Loading_Message" = "Unable to load message. Please try again later.";
"UA_Error_Loading_Message_List" = "Unable to load messages. Please try again later.";
Custom Inbox Implementations
While the Message Center is ideal for many use cases, there may be times when it’s more appropriate to embed a Message Center in a specific location within your app. While the Message Center attempts to strike a balance between configurability and hassle-free implementation, some implementation decisions can only be made on a case-by-case basis, such as placement and navigation.
In this section we’ll cover some key classes that are useful for creating custom implementations, and walk through one that embeds a Message Center in a tab. Though we won’t be using the Message Center as such, we can still use the UI classes it depends on. These can all be styled using the same UAMessageCenterStyle class covered in the section above, allowing for more rapid development.
Library Components
Custom Message Center implementations will depend on at least some combination of the classes and protocols described below. For additional detail, please see the Airship Library Reference.
UAMessageCenter
- UAMessageCenter is the main entry point for fetching and accessing messages, as well as signing up for delegate callbacks.
UAInboxMessageList
- UAInboxMessageList provides an interface for retrieving messages asynchronously, as well as a reference to the local message array itself. For instance, the example below shows how to retrieve new messages remotely:
MessageCenter.shared.messageList.retrieveMessageList(successBlock: {
// handle success
}, withFailureBlock: {
// handle failure
})
[UAMessageCenter.shared.messageList retrieveMessageListWithSuccessBlock:^{
// handle success
} withFailureBlock:^{
// handle failure
}];
If you are using or subclassing the default UI classes, this operation shouldn’t ordinarily be necessary. On the other hand, if you are designing custom UI, you can use the above call to implement refresh behavior. Accessing the local array of messages is straightforward:
let messages = MessageCenter.shared.messageList.messages
NSArray *messages = UAMessageCenter.shared.messageList.messages;
The message list uses CoreData under the hood, and as such the message objects contained within are ephemeral references, refreshed along with the list after retrieval. It is not recommended to hold onto individual instances indefinitely.
UAInboxMessage
- UAInboxMessage is the model object representing an individual message available for display. Instances of this class do not contain the actual message body. Rather, they point to the URL corresponding to the message body in the backend. These URLs are authenticated using UAUser credentials. Normally the default UI will handle most details involving message display transparently, but deep customizations ultimately involve displaying these URLs in a webview.
UAMessageCenterDisplayDelegate
- UAMessageCenterDisplayDelegate is a protocol that can be used to sign up for delegate callbacks when messages are available for viewing, or when UI should be shown. This can be useful in custom implementations that embed or subclass the default UI, as your app can use these events to navigate to the appropriate location when a new message (or the entire Message Center) needs to be displayed.
To register for callbacks, simply assign a delegate to the shared instance of UAMessageCenter :
MessageCenter.shared.displayDelegate = myDelegate
UAMessageCenter.shared.displayDelegate = self.myDelegate;
In the following sections we’ll walk through a custom implementation that uses this feature in more detail.
NativeBridge
- Most Message Center messages are rich HTML pages displayed in a web view. If you need to create a
custom implementation with your own webview,
UANativeBridge
(
WKWebView
) can be used to simplify integration with Airship features such as the Actions Framework.
Embed the Message Center UI
In this example we’ll be subclassing UADefaultMessageCenterSplitViewController .
import AirshipKit
class MessageCenterViewController : DefaultMessageCenterSplitViewController {
}
@import AirshipKit;
@interface MessageCenterViewController : UADefaultMessageCenterSplitViewController
@end
Assign a Custom Style
As a starting point, we can override the viewDidLoad
method to assign a custom
style to the view controller as we did above to the Message Center.
override func viewDidLoad() {
super.viewDidLoad()
let style = MessageCenterStyle()
let robotoLight = UIFont(name: "Roboto-Light", size: 12.0)
let robotoBold = UIFont(name: "Roboto-Bold", size: 14.0)
let robotoRegular = UIFont(name: "Roboto-Regular", size: 17.0)
style.navigationBarColor = UIColor(red: 0.988, green: 0.694, blue: 0.106, alpha: 1)
style.titleColor = UIColor(red: 0.039, green: 0.341, blue: 0.490, alpha: 1)
style.tintColor = UIColor(red: 0.039, green: 0.341, blue: 0.490, alpha: 1)
style.titleFont = robotoRegular
style.cellTitleFont = robotoBold
style.cellDateFont = robotoLight
self.style = style
}
- (void)viewDidLoad {
[super viewDidLoad];
UAMessageCenterStyle *style = [UAMessageCenterStyle style];
UIFont *robotoLight = [UIFont fontWithName:@"Roboto-Light" size:12.0];
UIFont *robotoBold = [UIFont fontWithName:@"Roboto-Bold" size:13.0];
UIFont *robotoRegular = [UIFont fontWithName:@"Roboto-Regular" size:17.0];
style.navigationBarColor = [UIColor colorWithRed:0.988 green:0.694 blue:0.106 alpha:1];
style.titleColor = [UIColor colorWithRed:0.039 green:0.341 blue:0.490 alpha:1];
style.tintColor = [UIColor colorWithRed:0.039 green:0.341 blue:0.490 alpha:1];
style.titleFont = robotoRegular;
style.cellTitleFont = robotoBold;
style.cellDateFont = robotoLight;
self.style = style;
}
Create and Register a custom UAMessageCenterDisplayDelegate
Implementing the UAMessageCenterDisplayDelegate protocol gives your app the chance to respond to events regarding Message Center display, or the display of individual messages within your Message Center. As this is an optional protocol, it also has the effect of overriding the Message Center implementation.
When the Message Center module receives a Message Center action, the first thing it does is check whether an message center delegate has been set. If not, the SDK will forward the action to the default Message Center implementation.
Create the Delegate
First, let’s create the delegate class. The structure of the class and design patterns used within are a matter of preference, but for an app built around a tab bar controller, one straightforward approach is to initialize it with the root view controller and use that at runtime to navigate to the appropriate tab when necessary.
import AirshipKit
class MessageCenterDelegate : NSObject, MessageCenterDisplayDelegate {
var tabBarController : UITabBarController;
var messageCenterViewController : MessageCenterViewController;
init(tabBarController:UITabBarController) {
self.rootViewController = rootViewController
}
func displayMessageCenter(forMessageID messageID: String!, animated: Bool) {
self.displayMessageCenter(animated: animated)
DispatchQueue.main.async {
self.messageCenterViewController.displayMessageForID(messageID)
}
}
func displayMessageCenter(animated: Bool) {
DispatchQueue.main.async {
self.tabBarController.selectedIndex = 2
self.messageCenterViewController.showInbox()
}
}
func dismissMessageCenter(animated: Bool) {
// no-op
}
}
#import "MessageCenterDelegate.h"
#import "MessageCenterViewController.h"
@interface MessageCenterDelegate ()
@property(nonatomic, strong) UITabBarController *tabBarController;
@property(nonatomic, strong) MessageCenterViewController *messageCenterViewController;
@end
@implementation MessageCenterDelegate
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController {
self = [super init];
if (self) {
self.tabBarController = (UITabBarController *)rootViewController;
self.messageCenterViewController = [self.tabBarController.viewControllers objectAtIndex:2];
}
return self;
}
- (void)displayMessageCenterAnimated:(BOOL)animated {
dispatch_async(dispatch_get_main_queue(), ^{
self.tabBarController.selectedIndex = 2;
[self.messageCenterViewController showInbox];
});
}
- (void)displayMessageCenterForMessageID:(NSString *)messageID animated:(BOOL)animated {
[self displayMessageCenterAnimated:animated];
dispatch_async(dispatch_get_main_queue(), ^{
[self.messageCenterViewController displayMessageForID:messageID];
});
}
- (void)dismissMessageCenterAnimated:(BOOL)animated {
// no-op
}
@end
Keep in mind that the index of 2 used above assumes that the
MessageCenterController occupies the third tab in the
application. Regardless of the implementation details, the fundamental
approach remains the same, which is that the delegate is responsible
for handling showInbox
and showInboxMessage
events, and
navigating to the tab containing the MessageCenter when needed.
Register the Delegate
Next, in order for the UAMessageCenterDisplayDelegate to actually receive those events, it must be registered. An easy place to do this would be in the AppDelegate, after takeOff.
func applicationDidFinishLaunching(_ application: UIApplication) {
// ...
// Set a custom delegate for handling Message Center events
self.messageCenterDelegate = MessageCenterDisplayDelegate(tabBarController: window!.rootViewController)
MessageCenter.shared.displayDelegate = self.messageCenterDelegate
// ...
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// ...
// Set a custom delegate for handling Message Center events
self.messageCenterDelegate = [[messageCenterDelegate alloc] initWithRootViewController:self.window.rootViewController];
UAMessageCenter.shared.delegate = self.messageCenterDelegate;
// ...
}
With these pieces in place, your embedded Message Center should be ready for use.
Categories