Author: Wang Handong

preface

Ios10 has made some major improvements to push, so let’s take a look at the logic of push presentation:

As shown in the figure above: Functionally, it can be divided into four modules:

A: Server push module

Decide the content to be pushed and the display scheme adopted by push (the display scheme is identified by category)Copy the code

B: show pretreatment module NotificationExtensionServie push arrived

The processing of this module is very fast, and the current recommended demo from Apple shows loading attached images. . In testing, you'll find that a push with an image arrives processed but not displayed. But it was shown in the second push. This is because servieExtension will display the existing push content directly, and files that need to be retrieved from the network can be cached for 30 seconds. So by the time basic Mountain arrives in the second push, the image has already been loaded and cachedCopy the code

C: NotificationExtensionContent push the middle layer interface processing

Customize function interfaces based on service requirementsCopy the code

D: Appdelegate.m Registers the button style

Register the button style in UNUserNotification to use when expanding the push middle page after the push arrives.Copy the code

Problems encountered during development

The biggest problem in the development is the button style. Each button combination can only be matched by one categoryIdentifier, resulting in the following problems:

  • Each business needs to have a button-style composition identifier.
  • The button style cannot change dynamically after each identifier is registered

At the same time, due to the timing of button registration (button combination style registration is registered in the appdelegate.m file), there are some problems:

  • The function of each button is determined in advance, resulting in less flexibility.
  • Line of business development must register buttons by changing common code, increasing functional coupling

At that time, the whole framework with these criticisms online, it is an incurable move. This problem has not been solved before, and a number of solutions have been verified in XcodeBeta, including the one below.

The solution

Recently, a detailed tracking of this problem has been made and a satisfactory solution has been found. Here’s the picture:

The idea comes from this image, which views the UNUserNotificationCenter as a separate structure from App and AppExtension, to which all App registration categories add button styles. So the question is, can you not register the information in appdelegate.m and put the category registration in extensionServie? Yes.

There are actually a couple of questions here, right?

1: Is there any difference between servie registration and APP registration? Can take effect?

2: What about registering the same categoryIdentifier twice? Was the push button style previously issued or changed?

3: Register categoryIdentifier one at a time, so the combination style of the previous push still exists?

The answer is this:

Is there a difference between registering in Servie and registering in app? Can take effect?

Servie registration and app registration are not different, can be effective

What about registering the same categoryIdentifier twice? Was the push button style previously issued or changed?

Registering the same categoryIdentifier repeatedly overwrites the original categoryIdentifier so that the need to change button styles for the same categoryIdentifier can be fulfilled. The following figure shows the printed log after the same categoryIdentifier push is issued

Print code implemented in ExtensionServie

@interface [[UNUserNotificationCenter currentNotificationCenter] getNotificationCategoriesWithCompletionHandler:^(NSSet<UNNotificationCategory *> * _Nonnull categories) { NSLog (@ "print here is that the category of information"); NSLog (@ "% @", categories);}]; @endCopy the code

So this is the push payload, and this is the payload bit, payloadOld

{" aps ": {" collapse" : "testKey", "alert" : {" title ":" payment alert ", "body" : "Your reservation at Aliva your reservation at Aliva your reservation at Aliva your reservation at Aliva your reservation at Aliva your reservation ",}," mutation-content ": 1, "sound":"default", "badge":1, "category":"MyZoneNotification", }, "actions":[ { "actionType":0, "ActionTitle" : "view the details of * * * = = = =", "actionOptions" : "foreground", "actionIdentifier" : "checkInfo", "actionScheme" : "* * * * * *,"}, {"actionType":1, "actionTitle":" reply ***====", "actionOptions":"foreground", "actionIdentifier":"applyInfo", "TextInputButtonTitle ":" reply button ", "textInputPlaceholder":" Input what you want to reply to ", "actionScheme":"******",}]}Copy the code

This is the log after sending a push

2016-11-23 11:50:03. 345871 RemoteNotificationTestServie [2073-191810] printed here is the category of information on the 2016-11-23 11:50:03. 346584 RemoteNotificationTestServie[2073:191810] {( <UNNotificationCategory: 0x127d27090; identifier: MyZoneNotification, actions: ( "<UNNotificationAction: 0x127d3bd20; identifier: checkInfo, title: \U67e5\U770b\U8be6\U60c5****====, isAuthenticationRequired: NO, isDestructive: NO, isForeground: NO>", "<UNTextInputNotificationAction: 0x127d396b0; identifier: applyInfo, title: \U56de\U590d***====, isAuthenticationRequired: NO, isDestructive: NO, isForeground: NO, textInputButtonTitle: \U56de\U590d\U7684\U6309\U94ae, textInputPlaceholder: \U8f93\U5165\U4f60\U60f3\U56de\U590d\U7684\U5185\U5bb9>" ), minimalAction: ( ), intentIdentifiers: ( check ), custom dismiss: NO, CarPlay: NO> )}Copy the code

So the payload actionTitle changes, the actionIdentifier changes. Payload indicates the bit payloadNew

{" aps ": {" collapse" : "testKey", "alert" : {" title ":" payment alert ", "body" : "Your reservation at Aliva your reservation at Aliva your reservation at Aliva your reservation at Aliva your reservation at Aliva your reservation ",}," mutation-content ": 1, "sound":"default", "badge":1, "category":"MyZoneNotification", }, "actions":[ { "actionType":0, "ActionTitle" : "view details", "actionOptions" : "foreground", "actionIdentifier" : "checkInfo * * *", "actionScheme" : "* * * * * *,"}, {"actionType":1, "actionTitle":" reply ", "actionOptions":"foreground", "actionIdentifier":"applyInfo====", "TextInputButtonTitle ":" reply button ", "textInputPlaceholder":" Input what you want to reply to ", "actionScheme":"******",}]}Copy the code

The log is generated after the action in payload is changed

2016-11-23 11:52:44. 074709 RemoteNotificationTestServie [2073-192382] printed here is the category of information on the 2016-11-23 11:52:44. 076397 RemoteNotificationTestServie[2073:192382] {( <UNNotificationCategory: 0x127d26a10; identifier: MyZoneNotification, actions: ( "<UNNotificationAction: 0x127d245f0; identifier: checkInfo***, title: \U67e5\U770b\U8be6\U60c5, isAuthenticationRequired: NO, isDestructive: NO, isForeground: NO>", "<UNTextInputNotificationAction: 0x127d265a0; identifier: applyInfo====, title: \U56de\U590d, isAuthenticationRequired: NO, isDestructive: NO, isForeground: NO, textInputButtonTitle: \U56de\U590d\U7684\U6309\U94ae, textInputPlaceholder: \U8f93\U5165\U4f60\U60f3\U56de\U590d\U7684\U5185\U5bb9>" ), minimalAction: ( ), intentIdentifiers: (Check), Custom dismiss: NO, CarPlay: NO>)} actionIdentifier has changedCopy the code

Was the style of the push button that was issued earlier or changed? The question was tested like this:

  • Issue 5 payloadOld,
  • Issue five payloadNew
  • Click to see the effect

From log analysis, it can be theoretically concluded that clicking on a push to expand the new coverage or clicking on an older push should show the style of the button for the latest push. But the actual result is this

This conclusion is different from the theoretical conclusion of log printing. There should be only one style of the category button after overwriting. Is the previous button still in the cache? . Imagine two situations to solve:

  • Wait long enough for the previous action cache to be released
  • Restart the phone

The answer is yes, after waiting 2 minutes to open the push of payloadOld, the button style is already up to date. Restart the phone and payloadOld is immediately up to date.

Test version

The mobile phone currently tested is ios10.2 beta3. Xcode is 8.2beta3. I need to verify again on release ios10.1.1

This problem also exists in ios10.1, which has been released after testing. Xcode8.1 release, ios10.1 release.

Register categoryIdentifier one at a time, so the combination style of the previous push still exists?

  • Emit five different categories, each carrying a different style
  • The number and information of categories in UNUserNotificationCenter are displayed

Here’s how to register a category in a push

@interface
- (void)registerCategory:(NSDictionary *)userInfo {
    NSParameterAssert(userInfo);
    NSString *categoryIdentifier = userInfo[@"aps"][@"category"];
    NSMutableArray *tempArray = [NSMutableArray new];
    NSArray *actions = userInfo[@"actions"];
    if (actions.count) {
        for (NSDictionary *action in actions) {
            if ([action[actionType] integerValue] == 0) {
                UNNotificationAction *tempAction = [self buildNotificationAction:action];
                if (tempAction) [tempArray addObject:tempAction];
            }else {
                UNTextInputNotificationAction *tempAction = [self buildTextNotification:action];
                if (tempAction) [tempArray addObject:tempAction];
            }
        }
    }
    if (tempArray.count > 0) {
        NSMutableSet *set = [NSMutableSet new];
        UNNotificationCategory *category = [UNNotificationCategory categoryWithIdentifier:categoryIdentifier actions:[tempArray copy] intentIdentifiers:@[@"check"] options:UNNotificationCategoryOptionNone];
        [set addObject:category];
        [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:set];
    }
}
@end
Copy the code

This method is the previous implementation and is registered using this method. It was found that when the new category push arrived, the previous hotel push would be washed away, in other words. Push center can only exist in the latest push category, look at the API description, found the problem: setNotificationCategories equivalent to reset the category. Therefore, only the latest push style can exist in the push center. There are two solutions to this problem:

  • Each time the push reaches, all the registration schemes required by the push will be delivered and registered together.
  • Payload Is the category that only carries this traffic at a time.

Each time the push reaches, all the registration schemes required by the push will be delivered and registered together

The question to consider under this scheme is the source of registration data:

  • payload
  • NotificationServieExtension download

If you use payload to send the registered button information, it increases the size of the payload. After ios8, the payload size is required to be less than 4kb. Another problem is that it is unreasonable to send out such a large payload every time (relatively large information for registering a Category), which will increase the traffic cost of users.

If using NotificationServieExtension download along the way, it will need to design a better scheme, this interface has a lot of security risks, after all the action can be sent to the app corresponding operation, so you need to do the security check. You can open up two background interfaces, one for detecting updates and one for storing pushed category content. The identifiers can be the version number and categoryID to update. When a user detects a new category, he or she downloads the latest resource and registers it locally. The problem with this is that the push may be successful, but the push file may not be downloaded successfully, and the category may not be registered successfully, resulting in the button style not being displayed.

Payload Is the category that only carries this traffic at a time

For push ontologies, there should not be the same categoryIdentifier either added or overwritten, so you need to check all categories in the current UNUserNotification. If the category exists, delete it and increment it; if it doesn’t, increment it

The code is as follows:

@interface
- (void)registerCategory:(NSDictionary *)userInfo {
    NSParameterAssert(userInfo);
    NSString *categoryIdentifier = userInfo[@"aps"][@"category"];
    NSMutableArray *tempArray = [NSMutableArray new];
    NSArray *actions = userInfo[@"actions"];
    if (actions.count) {
        for (NSDictionary *action in actions) {
            if ([action[actionType] integerValue] == 0) {
                UNNotificationAction *tempAction = [self buildNotificationAction:action];
                if (tempAction) [tempArray addObject:tempAction];
            }else {
                UNTextInputNotificationAction *tempAction = [self buildTextNotification:action];
                if (tempAction) [tempArray addObject:tempAction];
            }
        }
    }
    if (tempArray.count > 0) {
        [[UNUserNotificationCenter currentNotificationCenter] getNotificationCategoriesWithCompletionHandler:^(NSSet<UNNotificationCategory *> * _Nonnull categories) {
            NSMutableSet *set = [categories mutableCopy];
            for (NSInteger i = 0; i < set.allObjects.count; i ++) {
                UNNotificationCategory *category = set.allObjects[i];
                if ([category.identifier isEqualToString:categoryIdentifier]) {
                    [set removeObject:category];
                    break;
                }
                
            }
            UNNotificationCategory *category = [UNNotificationCategory categoryWithIdentifier:categoryIdentifier actions:[tempArray copy] intentIdentifiers:@[@"check"] options:UNNotificationCategoryOptionNone];
            [set addObject:category];
            [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:set];
        }];
    }
}
@end
Copy the code

At the time of registration, copy all existing categories to check whether there is duplication. If there is duplication, delete it; if there is not, add it. Look again at the log print result:

The 2016-11-23 16:21:01. 357154 RemoteNotificationTestServie [979-60218] {(< UNNotificationCategory: 0 x111d0b060; identifier: MyZoneNotification1, actions: ( "<UNNotificationAction: 0x111d11ca0; identifier: checkInfo1, title: \U67e5\U770b\U8be6\U60c51, isAuthenticationRequired: NO, isDestructive: NO, isForeground: NO>", "<UNTextInputNotificationAction: 0x111d07b20; identifier: applyInfo, title: \U56de\U590d1, isAuthenticationRequired: NO, isDestructive: NO, isForeground: NO, textInputButtonTitle: \U56de\U590d\U7684\U6309\U94ae, textInputPlaceholder: \U8f93\U5165\U4f60\U60f3\U56de\U590d\U7684\U5185\U5bb9>" ), minimalAction: ( ), intentIdentifiers: ( check ), custom dismiss: NO, CarPlay: NO>, <UNNotificationCategory: 0x111d09cf0; identifier: MyZoneNotification2, actions: ( "<UNNotificationAction: 0x111d09d20; identifier: checkInfo2, title: \U67e5\U770b\U8be6\U60c52, isAuthenticationRequired: NO, isDestructive: NO, isForeground: NO>", "<UNTextInputNotificationAction: 0x111d0b7a0; identifier: applyInfo, title: \U56de\U590d2, isAuthenticationRequired: NO, isDestructive: NO, isForeground: NO, textInputButtonTitle: \U56de\U590d\U7684\U6309\U94ae, textInputPlaceholder: \U8f93\U5165\U4f60\U60f3\U56de\U590d\U7684\U5185\U5bb9>" ), minimalAction: ( ), intentIdentifiers: ( check ), custom dismiss: NO, CarPlay: NO>, <UNNotificationCategory: 0x111d0ebf0; identifier: MyZoneNotification3, actions: ( "<UNNotificationAction: 0x111d0f500; identifier: checkInfo3, title: \U67e5\U770b\U8be6\U60c53, isAuthenticationRequired: NO, isDestructive: NO, isForeground: NO>", "<UNTextInputNotificationAction: 0x111d144e0; identifier: applyInfo, title: \U56de\U590d3, isAuthenticationRequired: NO, isDestructive: NO, isForeground: NO, textInputButtonTitle: \U56de\U590d\U7684\U6309\U94ae, textInputPlaceholder: \U8f93\U5165\U4f60\U60f3\U56de\U590d\U7684\U5185\U5bb9>" ), minimalAction: ( ), intentIdentifiers: ( check ), custom dismiss: NO, CarPlay: NO> )}Copy the code

There will now be three categories registered and three different button styles will be matched in the push

However, the advantage of this approach is that the business line can manage its own push style, decoupled from the public module, but it also has disadvantages. For example, if an IM application frequently sends such push, it is unnecessary to carry such resources

advice

  • If push messages are not very frequent, you can use the second scheme (payload is a category that only carries this traffic each time). It can reduce interface development and meet business requirements.
  • Push message very frequent situation can use the first kind of solutions of the second small (NotificationServieExtension download), reduce the volume content of the payload, less consumption of user traffic
  • In any case it is not recommended to use the first scheme of the first fire safety, there are too many disadvantages

The code shown

The following is an implementation in NotificationServie

#import "NotificationService.h" NSString *const actionTitle = @"actionTitle"; NSString *const actionType = @"actionType"; NSString *const actionOptions = @"actionOptions"; NSString *const actionIdentifier = @"actionIdentifier"; NSString *const textInputButtonTitle = @"textInputButtonTitle"; NSString *const textInputPlaceholder = @"textInputPlaceholder"; NSString *const actionScheme = @"actionScheme"; @interface NotificationService () @property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver); @property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent; @property (nonatomic, strong) NSDictionary *userInfo; @end @implementation NotificationService - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler { self.userInfo = request.content.userInfo; [self registerCategory:self.userInfo]; [[UNUserNotificationCenter currentNotificationCenter] getNotificationCategoriesWithCompletionHandler:^(NSSet<UNNotificationCategory *> * _Nonnull categories) { NSLog (@ "print here is that the category of information"); NSLog (@ "% @", categories);}]; self.contentHandler = contentHandler; self.bestAttemptContent = [request.content mutableCopy]; // Modify the notification content here... self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title]; self.contentHandler(self.bestAttemptContent); } - (void)serviceExtensionTimeWillExpire { [self registerCategory:self.userInfo]; self.contentHandler(self.bestAttemptContent); } - (void)registerCategory:(NSDictionary *)userInfo { NSParameterAssert(userInfo); NSString *categoryIdentifier = userInfo[@"aps"][@"category"]; NSMutableArray *tempArray = [NSMutableArray new]; NSArray *actions = userInfo[@"actions"]; if (actions.count) { for (NSDictionary *action in actions) { if ([action[actionType] integerValue] == 0) { UNNotificationAction *tempAction = [self buildNotificationAction:action]; if (tempAction) [tempArray addObject:tempAction]; }else { UNTextInputNotificationAction *tempAction = [self buildTextNotification:action]; if (tempAction) [tempArray addObject:tempAction]; } } } if (tempArray.count > 0) { [[UNUserNotificationCenter currentNotificationCenter] getNotificationCategoriesWithCompletionHandler:^(NSSet<UNNotificationCategory *> * _Nonnull categories) { NSMutableSet *set = [categories mutableCopy]; for (NSInteger i = 0; i < set.allObjects.count; i ++) { UNNotificationCategory *category = set.allObjects[i]; if ([category.identifier isEqualToString:categoryIdentifier]) { [set removeObject:category]; break; } } UNNotificationCategory *category = [UNNotificationCategory categoryWithIdentifier:categoryIdentifier actions:[tempArray copy] intentIdentifiers:@[@"check"] options:UNNotificationCategoryOptionNone]; [set addObject:category]; [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:set]; }]; } } - (UNNotificationAction *)buildNotificationAction:(NSDictionary *)action { NSParameterAssert(action); NSString *title = action[actionTitle]; NSString *identifier = action[actionIdentifier]; NSString *optionsString = action[actionOptions]; UNNotificationActionOptions options; if ([optionsString isEqualToString:@"acptions"]) { options = UNNotificationActionOptionForeground; } return [UNNotificationAction actionWithIdentifier:identifier title:title options:options]; } - (UNTextInputNotificationAction *)buildTextNotification:(NSDictionary *)action { NSParameterAssert(action); NSString *title = action[actionTitle]; NSString *identifier = action[actionIdentifier]; NSString *buttonTitle = action[textInputButtonTitle]; NSString *placeholder = action[textInputPlaceholder]; NSString *optionsString = action[actionOptions]; UNNotificationActionOptions options; if ([optionsString isEqualToString:@"acptions"]) { options = UNNotificationActionOptionForeground; } return [UNTextInputNotificationAction actionWithIdentifier:identifier title:title options:options textInputButtonTitle:buttonTitle textInputPlaceholder:placeholder]; } @endCopy the code

payload Example:

{" copy ":{" copy ":{" copy ":{" copy ":{" copy ":{" copy ":{" copy ":{" copy ":{" copy ":{" copy ":{" copy ":{" copy ":{" copy ":{" copy ":{" copy ":{" copy ":{ 1, "sound":"default", "badge":1, "category":"MyZoneNotification", "collapse":"testKey" }, "actions":[ { "actionType":0, "ActionTitle" : "for details * * * * = = = =", "actionOptions" : "foreground", "actionIdentifier" : "checkInfo", "actionScheme" : "* * * * * *,"}, {"actionType":1, "actionTitle":" reply ***====", "actionOptions":"foreground", "actionIdentifier":"applyInfo", "TextInputButtonTitle ":" reply button ", "textInputPlaceholder":" Input what you want to reply to ", "actionScheme":"******",}]}Copy the code

conclusion

Registered in NotificationServieExtension Category can bring? First let’s take a look at the drawbacks of registering a category in Appdelegate.m:

  • Can not be updated in real time, need to send version to update
  • Business code is coupled to common functionality.

Registered in NotificationServieExtension advantage

  • Change button styles in real time. Overrides the button style combination for the specified categoryIdentifier
  • Business functions are decoupled from common components. The real-time registration button is displayed based on payload information.
  • Real-time change button functionality. The functions of buttons can also be carried by payload