Search
Follow
Recent Comments

Entries in iAd (11)

Sunday
Nov282010

Ad inventory unavailable with iOS 4.2

I have had some real issues testing the iAd framework since Apple released iOS 4.2 (and the 4.2.1 update). I mentioned yesterday the changes required to also support the iPad. However, when testing either in the simulator or on a device running 4.2.x the test iAd is never displayed. I have not managed to resolve this issue yet but since I guess lots of people are suffering this same problem I thought I would share the pain…

ADBannerViewDelegate Methods

The ADBannerViewDelegate protocol has two methods that are called to allow the iAd framework to inform the application when an advertisement is loaded or when an error occurs. When everything is working the bannerViewDidLoadAd: method is called to inform the application that it can show the banner view.

When running a developer build of an application what should happen is that a test iAd is eventually returned. I say eventually because Apple do say that they will sometimes simulate a failure to retrieve an Ad to allow the developer to properly test both success and failure cases. However within a few minutes you should get a test iAd.

If the iAd framework encounters an error when attempting to retrieve an Ad it calls the bannerView:didFailToReceiveAdWithError: method. This method includes an NSError object that can be used to determine the reason for the failure. The easiest way to do this is to add an NSLog call to log the error description to the console:

- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error {
	
    NSLog(@"bannerView:didFailToReceiveAdWithError: %@",[error localizedDescription]);
    [self hideBanner];
}

Ad Inventory Unavailable error messages

Running the application on the simulator with iOS 4.2 writes the following message to the Xcode console:

bannerView:didFailToReceiveAdWithError: The operation couldn’t be completed. Ad inventory unavailable

A similar result occurs when running on a device (in this case a fourth generation iPod Touch) running iOS 4.2.1. Also what is interesting is that when running on a device that is still at iOS 4.1 the problem does not exist - the test iAd is immediately displayed. Deploying to the simulator running iOS 4.1 also results in a test iAd being displayed so it does seem to be a problem related to iOS 4.2.

What is frustrating is that I have once or twice managed to get a test iAd to appear on the device running iOS 4.2 but not in a repeatable way. Looking through the Apple developer forums would seem to indicate that many people are experiencing this issue and as yet nobody has figured out the root cause. Also what is not clear is if this issue is only with test Ads or also impacts production Ads.

Apple is in the process of rolling out iAd’s to a number of additional countries so you may be tempted to think that this problem is related to that and will eventually fix itself. However I doubt the rollout of the production service should have any impact on the test iAds. If you are experiencing this issue my only suggestion right now is to file a bug report with Apple and hope they figure it out soon. If I find a solution I will of course update this post….

Saturday
Nov272010

iAd Framework updates for iOS 4.2

A quick look at the changes made to the iAd framework in iOS 4.2 to accommodate the larger iPad screen size.

Ad Banner View Content Size

The release of iOS 4.2 means that we finally have a unified OS running across iPad, iPhone and iPod Touch devices. This means that iOS features such as multi-tasking and folders finally come to the iPad. It also means that iAd is now available to applications running on the iPad.

From a developer perspective the only significant change to the iAd framework in iOS 4.2 is to allow for the increased screen space of the iPad. The iAd Banner view dimensions for the iPhone and iPad are as follows:

  • iPhone: 320 x 50 points (portrait), 480 x 32 points (landscape)
  • iPad: 766 x 66 points (portrait), 1024 x 66 points (landscape)

For an example on how to create the iAd Banner View you can refer back to the original TabNav example app (updated here and here). The basic code to create a banner view prior to iOS 4.2 is as follows:

- (void)createBannerView {
	
    Class cls = NSClassFromString(@"ADBannerView");
    if (cls) {
        ADBannerView *adView = [[cls alloc] initWithFrame:CGRectZero];
        adView.requiredContentSizeIdentifiers = [NSSet setWithObjects:
                                                  ADBannerContentSizeIdentifier320x50,
                                                 ADBannerContentSizeIdentifier480x32,
                                                 nil];

        // Set the current size based on device orientation
        adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifier320x50;
        adView.delegate = self;
		
        adView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | 
                                  UIViewAutoresizingFlexibleRightMargin;
		
        // Set intital frame to be offscreen
        CGRect bannerFrame =adView.frame;
        bannerFrame.origin.y = self.view.frame.size.height;
        adView.frame = bannerFrame;
		
        self.bannerView = adView;
        [self.view addSubview:adView];
        [adView release];
    }
}


When creating the banner view you need to specify the required sizes. Since the sizes will be different between the iPhone and the iPad the previous identifiers ADBannerContentSizeIdentifier320x50 and AdBannerContentSIzeIdentifier480x32 are now deprecated. If you compile against the iOS 4.2 SDK you will see the following runtime warning messages in Xcode:


ADBannerView: ADBannerContentSizeIdentifier480x32 is deprecated, please 
              use ADBannerContentSizeIdentifierLandscape instead
ADBannerView: ADBannerContentSizeIdentifier320x50 is deprecated, please 
              use ADBannerContentSizeIdentifierPortrait instead


If you only intend to support iOS 4.2 the solution is, as suggested in the deprecation message, to use the ADBannerContentSizeIdentifierPortrait and ADBannerContentSizeIdentifierLandscape symbols instead. This will ensure that the correct banner view size is used for both the iPad and the iPhone.

In the example we are also targeting older versions of iOS all the way back to 3.1 (of course iAds will not be displayed on iOS 3.1 but we do not want the App to crash). Since these new symbols are not available on iOS versions 4.1 and older we need to do some additional runtime checks. The modified createBannerView method therefore becomes as follows:

- (void)createBannerView {
	
    Class cls = NSClassFromString(@"ADBannerView");
    if (cls) {
		
        if (&ADBannerContentSizeIdentifierPortrait != nil) {
            kTabnavADBannerContentSizeIdentifierPortrait = 
ADBannerContentSizeIdentifierPortrait;
        } else {
            kTabnavADBannerContentSizeIdentifierPortrait = 
ADBannerContentSizeIdentifier320x50
;
        }
		
        if (&ADBannerContentSizeIdentifierLandscape != nil) {
            kTabnavADBannerContentSizeIdentifierLandscape = 
ADBannerContentSizeIdentifierLandscape
;
        } else {
            kTabnavADBannerContentSizeIdentifierLandscape = 
ADBannerContentSizeIdentifier480x32;
        }
		
        ADBannerView *adView = [[cls alloc] initWithFrame:CGRectZero];
        adView.requiredContentSizeIdentifiers = [NSSet setWithObjects:
                                                 kTabnavADBannerContentSizeIdentifierPortrait,
                                                 kTabnavADBannerContentSizeIdentifierLandscape,
                                                 nil];

        // Set the current size based on device orientation
        adView.currentContentSizeIdentifier = kTabnavADBannerContentSizeIdentifierPortrait;
        adView.delegate = self;

        adView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin |
                                   UIViewAutoresizingFlexibleRightMargin;

        // Set intital frame to be offscreen
        CGRect bannerFrame =adView.frame;
        bannerFrame.origin.y = self.view.frame.size.height;
        adView.frame = bannerFrame;
		
        self.bannerView = adView;
        [self.view addSubview:adView];
        [adView release];
    }
}

The trick here is to test if the ADBannerContentSizeIndentifierPortrait and ADBannerContentSizeIndentifierLandscape symbols exist and if they do not fallback to the old symbols. The correct symbols are used to set two additional NSString references defined in the view controller class implementation:

NSString *kTabnavADBannerContentSizeIdentifierPortrait;

NSString *kTabnavADBannerContentSizeIdentifierLandscape;

 

Once these two NSString references are set in createBannerView they are then used in place of the iAd framework defined values. The only other place that the banner view size needs to be set is when responding to orientation changes:

- (void)changeBannerOrientation:(UIInterfaceOrientation)toOrientation {
	
    if (UIInterfaceOrientationIsLandscape(toOrientation)) {
        self.bannerView.currentContentSizeIdentifier
             kTabnavADBannerContentSizeIdentifierLandscape;
    } else {
        self.bannerView.currentContentSizeIdentifier
             kTabnavADBannerContentSizeIdentifierPortrait;
    }
}

You can find the example Xcode project updated for iOS 4.2 here if you want to review the complete project.

Thursday
Sep162010

Placing iAd banners at the top of a table view

I had a question on my post about adding iAds to an application asking how to show the iAd banner view at the top of the table view rather than at the bottom. Since explaining code changes in the comments of a post is never that easy I thought I would quickly cover it in a new post.

The changes are actually fairly minimal so I will not go through the whole example from scratch. Refer back to the original posting for the full details. I have also uploaded a second version of the Xcode project for the example app if you want to see the whole thing.

Resizing the table view

The basic idea when displaying an iAd banner view above or below a table view is to introduce a container view to hold both the table and iAd views. The iAd banner view is positioned off screen until we want to display it - which is usually when a new Ad is supplied by the iAd network. When displaying or hiding the iAd banner view the table view is resized to create or hide a space for the iAd.

To ensure the table view resizes correctly you need to set its autosizing mask correctly in the FirstView.xib file. With the iAd placed below the table we make the bottom strut flexible in IB. For the iAd placed above the table make the top strut flexible.

In fact there is no harm in making both the upper and lower struts flexible so that you can easily switch the iAd banner view between positions above and below the table.

Resizing the iAd Banner View

As well as resizing the table view the iAd banner view will also resize when the device orientation changes. When the iAd is as the bottom of the view it needs to have flexible top and right margins. This is set in the createBannerView method of the view controller (FirstViewController.m).

  adView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | 
  UIViewAutoresizingFlexibleRightMargin;

When we move the iAd banner to the top of the view we need to give it a flexible bottom and right margins. So the code becomes:

  adView.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin |
  UIViewAutoresizingFlexibleRightMargin;

Creating the iAd Banner View off-screen

In the original version the iAd banner is positioned offscreen below the end of the table view. Whilst there is really no reason to change that I will modify the createBannerView to position the view offscreen above the table view. This is actually trivial to achieve, you just need to set the y-coordinate of the view frame to be minus the height of the view:

  bannerFrame.origin.y = - bannerFrame.size.height;

Showing the iAd Banner View

To show the iAd banner view at the top of the table view, the table view has to be resized and then have its origin moved down by the size of the iAd banner view:

  tableFrame.size.height = fullViewHeight - bannerFrame.size.height;
  tableFrame.origin.y = bannerFrame.size.height;
 

The banner view is then slid down into place by setting its y-coordinate to zero so that it is at the top of the frame filling the space left by the table view:

  bannerFrame.origin.y = 0;

Hiding the iAd Banner View

To hide the iAd banner view the table view is resized to fill the view and its origin reset to the top of the frame:

  tableFrame.size.height = fullViewHeight;
  tableFrame.origin.y = 0;
 

The banner view is then also moved back to its offscreen postion:

  bannerFrame.origin.y = - bannerFrame.size.height;

Wrapping Up

That’s all there is to it. As I say the changes are pretty minimal but I thought it was interesting to see how to modify the app. The modified version of the Xcode project is available for download here.

Sunday
Aug082010

Checking network connectivity when displaying iPhone iAds

There was a comment to my post about adding iAds to an application that got me thinking about an improvement to the way I handle the iAd BannerView. The comment was about moving the app to the background, entering airplane mode and then moving the app back to the foreground. That is just one example of the more general problem of changing network connectivity. The problem comes after you have successfully requested and received an Ad and shown the banner view. If at that point network connectivity is lost the banner view is left on screen. If the user clicks on the Ad banner they get a network connection error which is not exactly a great user experience.

You could reason that losing network connectivity after you have displayed the ad banner is not very likely and just live with the problem - after all the app still works, the user just gets an error message. Note that you need to have network connectivity to see the add in the first place as it is only moved onscreen when the request for an ad completes with success. However with iOS 4 the chances of network connectivity changing whilst your app is running is much more likely. An app can spend days suspended in the background before being brought back to the foreground. In that time the user can be in a completely different location with or without network connectivity. Leaving the ad banner onscreen when there is no network connection wastes some screen space than can be put to better use and as I said earlier hardly represents the best user experience we can provide.

Using Reachability to Check Network Connectivity

To summarise the problem I want to be able to control when an iAd banner is displayed or hidden based on whether the device has network connectivity. Thus if the device has no network connectivity I do not bother to request an Ad since it will return an error anyway. Likewise if I am showing the banner and lose connectivity I remove it.

The network state of an iPhone can be be monitored using the SCNetworkReachability interface of the System Configuration framework. Apple provides the Reachability sample application as an example on how to implement the framework which makes it trivial to add to an existing app. The example app was recently updated for iOS 4.0 so if you already have a copy it may be worth checking you are using the latest version (v2.2).

To get started add the reachability code and SystemConfiguration.Framework to the example app I previously used to demonstrate implementing iAds. Copy the Reachability.h and Reachability.m files from the Apple Reachability sample app and use the Add -> Existing Files and Add->Existing Frameworks options in Xcode to add the files and framework to the project:

Reachability Class Setup

The Reachability class provides a convenient wrapper to the SCNetworkReachability interface. To start using it we just need to add an ivar to the application delegate to hold a reachability object and then start the notifier when the application first launches (existing code not shown):

//  tabnavAppDelegate.h

@class Reachability;

@interface TabnavAppDelegate : NSObject <UIApplicationDelegate, UITabBarControllerDelegate> {
    ...
    ...
    Reachability *netReach;
}
...
...
@property (nonatomic, retain) Reachability *netReach;

 

//  tabnavAppDelegate.m
#import "Reachability.h"

@implementation TabnavAppDelegate

@synthesize netReach;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
                                                  (NSDictionary *)launchOptions { 

    self.netReach = [[Reachability reachabilityWithHostName: @"apple.com"] retain];
    [netReach startNotifier];
    ...
    ...
}

- (void)dealloc {
    [netReach release];
    ...
    ...
}

 

The reachabilityWithHostName class method takes the name of a host to check - apple.com in this case - and returns a reachability object that we can use to check the network status. The reachability class can also send notifications when the reachability status changes. The startNotifier method instructs the reachability class to start sending reachability changed notifications.

Listening for Reachability Changed Notifications

With the reachability class initialised we can use it in our view controller to check the reachability status and control when to show and hide the iAd BannerView. First I will add a helper method to query the Reachability object to determine if we currently have network connectivity:

- (BOOL)networkReachable {
	
    TabnavAppDelegate *delegate = (TabnavAppDelegate *)[[UIApplication sharedApplication]
                                                        delegate];
    Reachability *netReach = [delegate netReach];

    NetworkStatus netStatus = [netReach currentReachabilityStatus];
    BOOL connectionRequired = [netReach connectionRequired];
	
    if ((netStatus == ReachableViaWiFi) ||
        ((netStatus == ReachableViaWWAN) && !connectionRequired)) {

        return YES;
    }

    return NO;
}

 

This method first retrieves the reachability object we setup in the application delegate and then uses the currentReachabilityStatus and connectionRequired methods to determine network connectivity. There are several possible situations since the device may be connected via WiFi or via the cellular network. Also the cellular network may be available but not actually connected.

We can now use this method to determine the network status when our view controller first loads. If we already have network connectivity we can go ahead and create the iAd BannerView immediately. In addition we will initialise an observer to listen for reachability changed notifications generated by the Reachability class. Each time the network connectivity of the device changes we will get a call to the method reachabilityChanged that we specified when creating the observer:

- (void)viewDidLoad {

    // If the network is reachable create the iAd banner, otherwise we
    // wait until the network becomes reachable
    BOOL reachable = [self networkReachable];
	
    if (reachable) {
        [self createBannerView];
    }

    // Listen for reachability changes
    [[NSNotificationCenter defaultCenter] addObserver:self
                                          selector:@selector(reachabilityChanged:) 
                                          name:kReachabilityChangedNotification
                                          object:nil];
}

 

Note that whenever you add an observer for a notification you need to ensure to remove the observer when your view controller is deallocated:

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    ...
    ...
}

 

To finish up we just need to implement the reachabilityChanged method that is called whenever a reachability notification is received:

- (void)reachabilityChanged:(NSNotification *)note {

    BOOL reachable = [self networkReachable];
	
    if (reachable && (self.bannerView == nil)) {
        [self createBannerView];
    }

    if (!reachable && self.bannerView) {
       [self hideBanner];
       [self releaseBanner];
    }
}

 

This method determines the current reachability status using the helper method we created earlier. Then if the network has just become reachable and we do not currently have a bannerView it is created. Alternatively if network connectivity has just been lost and we currently have a bannerView it is hidden and released. This means that during the life of the application we will add and remove the bannerView as the network connectivity changes.

There are several different strategies we could have used here that would achieve the same result from the users perspective. For example, we could always create the bannerView in viewDidLoad and then call hideBanner and showBanner in reachabilityChanged instead of creating and releasing each time.

To test that everything is working you can try it on a network connected device. Once the test iAd banner appears you can disconnect the network connection using the settings application (or use airplane mode) and you should see the Ad disappear. Likewise when you restore connectivity you should eventually see a new ad banner.

Handling Background App Switching

One other enhancement I considered adding was to release the BannerView when the app is switched to the background and recreate it when moving back to the foreground. This is easy enough to implement by having our view controller listen for UIApplicationDidEnterBackgroundNotification and UIApplicationDidBecomeActiveNotification. However when I checked the background memory usage after releasing the bannerView I found it made very little difference so I have left this out of the sample app.

All Done

That’s all for now you can find the updated sample app here. Let me know what you think.

Thursday
Jul222010

iPhone Analytics Updates and News

Flurry Analytics SDK 2.6

More news from Flurry that they may finally have resolved the iOS 4 compatibility issues. To recap the problem I and many others were seeing on version 2.5 of the SDK. Quitting and restarting the app too quickly could sometimes cause app crashes or leave the user looking at a blank screen. This was especially true in situations with high network latency.

In the latest release of the SDK (2.6) they have made further changes to play better with iOS 4 multitasking. Analytics data is now sent, by default, when the app starts, resumes and terminates. Sending the data when the app terminates is a possible source of app crashes so it seems like a good idea to keep that disabled - though on iOS 4 the app never really terminates so it should be less of an issue.

I have been doing some testing of this latest version on both iOS 4 and iOS 3.1.3 to see if it improves the situation. I have not been able to reproduce the issue with the blank screen so it seems like they have resolved that problem. There have also been some reports that startup time is impacted but I guess it could be dependent on network response times as I cannot say I have noticed anything yet.

Note that Flurry still do not promise that Apple will not decide to reject your app:

Despite our latest efforts, please understand that we are unable to guarantee whether Apple reviewers will approve your application in its App Store submission process. 

MixPanel

In related news it looks like Flurry is getting some more competition from MixPanel who also now have an iPhone analytics offering. Based on just a quick look it seems to be very similar requiring the addition of a library and then one line of code to initialise and start collecting data. Like Flurry it also allows you to define and log events within the app. I did not get a chance to do any testing with MixPanel so I can’t say if it suffers from any of the same stability problems.

One big difference is that the service is not completely free. Pricing is based on the number of data points you collect each month. Up to 10,000 data points is free though you can get up to 100,000 free if you place a mixpanel partner badge on your website.

Do you need third party analytics?

It can certainly be valuable to know how often users are running your app, how long they run it for and what parts of the app they visit (or don’t visit). It was also extremely useful to have data on OS versions but that is now no longer allowed by Apple. On the downside there is the risk of creating stability problems and increased application startup times.

One way of getting some very simple analytics is through the use of iAds. This in no way compares with the offerings from Flurry or MixPanel but it carries less risk. If you are using iAds you can use the iAd Network dashboard in iTunes Connect to view the number of Ad requests each application makes daily grouped by country.

Using iAds gives only a very crude idea of app usage by geography - not great but better than nothing if you do not want to risk a third party library. Also since iAds is an iOS 4 feature you will not see iPad or older OS usage. Maybe Apple will improve the data they share with developers but in the meantime if you are not using iAds or want some real data on app usage you probably need to look at something like Flurry or MixPanel.