Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

RagingGoat

macrumors 6502
Original poster
Jun 21, 2010
307
15
My app receives push notifications when an RSS feed in my app is updated. When the app is launched from the notification, that RSS feed opens. When the app is in the foreground, an alert view is shown. If the app is not in the foreground and is not opened from the notification, a badge icon appears on the menu table view in the cell to open that RSS feed. When the cell is selected the app badge icon is reset and the icon in the cell is removed.

I'm planning on adding notifications for things other than the RSS feed, such as member benefits, but I'm having trouble with showing the badge icon in the table view. My notificationType string is always null so there is never a badge icon placed in any cell in the menu table view.

Code:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    UA_LINFO(@"Received remote notification: %@", userInfo);
    
    [[UAPush shared]appReceivedRemoteNotification:userInfo applicationState:application.applicationState];
    
    if (application.applicationState == UIApplicationStateActive) {
        [[UAPush shared] resetBadge];
    }
    
    if (application.applicationState == UIApplicationStateInactive || application.applicationState == UIApplicationStateBackground) {
        NSDictionary *apsInfo = [userInfo valueForKey:@"aps"];
        
        if ([apsInfo valueForKey:@"alert"] != NULL) {
            self.alert = [apsInfo valueForKey:@"alert"];
            
            if ([self.alert containsString:@"ACTION ALERT"]) {
                self.notificationType = @"action alert";
            }
            else if ([self.alert containsString:@"MEMBER BENEFIT"]) {
                self.notificationType = @"member benefit";
            }
        }
    }
}

In cellForRowAtIndexPath: in my menu table view.
Code:
KFBAppDelegate *appDelegate = (KFBAppDelegate *)[[UIApplication sharedApplication]delegate];
    badgeNumber = [NSString stringWithFormat:@"%ld", (long)[[UIApplication sharedApplication]applicationIconBadgeNumber]];
    actionAlertBadge = [JSCustomBadge customBadgeWithString:badgeNumber withStringColor:[UIColor whiteColor] withInsetColor:[UIColor redColor] withBadgeFrame:NO withBadgeFrameColor:[UIColor redColor] withScale:1.0 withShining:NO withShadow:NO];
    actionAlertBadge.frame = CGRectMake(83, 6, 30, 30);
    
    if ([badgeNumber isEqualToString:@"0"]) {
        actionAlertBadge.hidden = YES;
    }
    
    if (actionAlertBadge.hidden == NO) {
        if ([appDelegate.notificationType isEqualToString:@"action alert"]) {
            if (indexPath.section == 0) {
                if (indexPath.row == 0) {
                    cell.accessoryView = actionAlertBadge;
                }
            }
        }
        else if ([appDelegate.notificationType isEqualToString:@"member benefit"]) {
            if (indexPath.section == 0) {
                if (indexPath.row == 5) {
                    cell.accessoryView = actionAlertBadge;
                }
            }
        }
    }
 
Is there a reason why you are using the cell's accessoryView instead its imageView?

Is your cells' accessoryView tapable? That is, are you going perform a different action when the accessory is tapped vs. when the row is selected?

It seems to me that if you want to badge a table row (which I do in my app also) it's because you want to call the user's attention to that row. And it seems like the badge would be more easily noticeable on the left of the table instead of the right. I say that because the accessoryView is on the right and the imageview is on the left. And that would be my reason for using the imageView.

It looks like you are numbering each badge too, is there are reason for that?

After reading your post a little more carefully and taking a glance at the docs...

Have you inspected your apsInfo dictionary to see what's in it?
Have you verified your notification contains everything it should contain when received?

I also found this nugget in the docs:

Implement the application:didReceiveRemoteNotification:fetchCompletionHandler: method instead of this one whenever possible. If your delegate implements both methods, the app object calls the application:didReceiveRemoteNotification:fetchCompletionHandler: method.
 
Last edited:
I am using the accessoryView because the imageView is already being used, and no, the accessoryView is not tappable.
 
I am using the accessoryView because the imageView is already being used, and no, the accessoryView is not tappable.

Very good. Are you allowed to add a small badge to the image in the imageView? (That's what I do.)

I also noticed you are doing this in your cellForRowAtIndexPath: implementation:

Code:
KFBAppDelegate *appDelegate = (KFBAppDelegate *)[[UIApplication sharedApplication]delegate];

I would make appDelegate a property on your tableView and then allocate and initialize it in viewDidLoad:

You'd get better performance from your tableView (which may or may not be noticeable.)
 
I also noticed you are doing this in your cellForRowAtIndexPath: implementation:

Code:
KFBAppDelegate *appDelegate = (KFBAppDelegate *)[[UIApplication sharedApplication]delegate];

I would make appDelegate a property on your tableView and then allocate and initialize it in viewDidLoad:

You'd get better performance from your tableView (which may or may not be noticeable.)

I will do that. Any ideas why I'm not getting my desired result though. my notificationType string is always null so I never see the badge.
 
I will do that. Any ideas why I'm not getting my desired result though. my notificationType string is always null so I never see the badge.

I would start by setting a break point and inspect the notification when it gets received and make sure that everything is there that needs to be there. I.E. make sure the notification is getting sent correctly to begin with.
 
I am doing this in didFinishLaunchingWithOptions: and it works perfectly fine so I know the payload is ok.

Code:
// If application is launched due to  notification,present another view controller.
    UILocalNotification *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
    
    if (notification)
    {
        NSDictionary *userInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
        NSDictionary *apsInfo = [userInfo valueForKey:@"aps"];
        
        NSString *alertMsg = @"";
        
        if ([apsInfo valueForKey:@"alert"] != NULL) {
            alertMsg = [apsInfo valueForKey:@"alert"];
            
            if ([[UIDevice currentDevice]userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
                if ([alertMsg containsString:@"ACTION ALERT"]) {
                    ActionAlertsViewController *rootView = [[ActionAlertsViewController alloc] initWithNibName:nil bundle:nil];
                    [rootView fetchEntries];
                    WebViewController *wvc = [[WebViewController alloc]init];
                    [rootView setWebViewController:wvc];
                    
                    KFBNavControllerViewController *navController = [[KFBNavControllerViewController alloc] initWithRootViewController:rootView];
                    navController.delegate = rootView;
                    
                    self.window.rootViewController = navController;
                }
                else if ([alertMsg containsString:@"MEMBER BENEFIT"]) {
                    MemberBenefits *rootView = [[MemberBenefits alloc] initWithNibName:nil bundle:nil];
                    
                    KFBNavControllerViewController *navController = [[KFBNavControllerViewController alloc] initWithRootViewController:rootView];
                    navController.delegate = rootView;
                    
                    self.window.rootViewController = navController;
                }
            }
        }
 
Based on this info from the docs:

alert—The value is either a string for the alert message or a dictionary with two keys: body and show-view. The value of the body key is a string containing the alert message and the value of the show-view key is a Boolean. If the value of the show-view key is false, the alert’s View button is not shown. The default is to show the View button which, if the user taps it, launches the app.

I would expand your test:

Code:
[apsInfo valueForKey:@"alert"] != NULL

I would also test whether resulting value from the above line is a string or a dictionary.

edit #1:

After thinking about it, your app probably would have crashed on this line:
Code:
[self.alert containsString:@"ACTION ALERT"]
if self.alert were a dictionary.
(I would still make sure self.alert is a string before calling that method on it.)

I would check your message body in the debugger and make sure it really contains the strings you are checking for.

You might add an else "clause" to your if/then block to handle the case where the message body does not contain any of the strings you are parsing for.

Is the generation of the RSS feed under your control? Maybe check for typos at the source of the message?
 
Last edited:
I have verified that the alert contains one of the desired strings. If I'm using the app and a notification comes through, an alert view is displayed and I see in the console that didReceiveRemoteNotification: is called and my notificationType string is given the appropriate value. The problem seems to be didReceiveRemoteNotification: not being called in the scenarios I'm testing. I'm probably doing something wrong. For example, the user backgrounds the app by pressing the home button. A notification then comes through. The user taps the app icon to open the app. In this case didReceiveRemoteNotification: is never called. So, what should I do? Another scenario is that the user opens the app from multitasking by double tapping the home button. didReceiveRemoteNotifications: is still not called.
 
Did the solution involve something like the following?
(Code found on StackOverflow)

Code:
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
    bgTask = UIBackgroundTaskInvalid;
};

// do your background task

NSLog(@"beginBG called");
[app endBackgroundTask:bgTask];

edit:

And make sure you add the remote notifications key to the UIBackgroundModes array in the info.plist:

From the docs:
remote-notification
The app uses remote notifications as a signal that there is new content available for download. When a remote notification arrives, the system launches or resumes the app in the background and gives it a small amount of time to download the new content.
This value is supported in iOS 7.0 and later.

If you already solved it, please let us now what did the trick.
 
Last edited:
I take back what I said. I don't have it figured out. I tried
Code:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
and adding remote notifications to background modes but still nothing. didReceiveRemoteNotifications never gets called unless I'm actually using the app.
 
OK, after reading the docs a little further, this method is only called in the foreground:

Code:
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo

So you definately want the newer method which I see you have tried:

Code:
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler

And just in case you didn't already see this (I didn't until just now) here's some important info from the docs:

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 push notifications may not always be woken up early to process future notifications.

You say didReceiveRemoteNotification: is not called and in the background and that is true. Have you verified that didReceiveRemoteNotification:fetchCompletionHandler: is not called in the background and if so, how? Where are you placing the breakpoint or NSLog?

The docs say didReceiveRemoteNotification:fetchCompletionHandler: is called in the background and one would think that means all the code in it gets executed. You might try something like the following in your implementation of that method. I'm not even sure if this is proper/necessary/desirable, but I'd say give it a shot and see if it works:

Code:
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
    bgTask = UIBackgroundTaskInvalid;
};

// process your notification here

NSLog(@"beginBG called"); //<- You can use a breakpoint to log messages to the console and actually I think it's better than NSLog

[app endBackgroundTask:bgTask];

edit:

And silly me. I forgot to ask which iOS you are using.
 
Last edited:
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.