Adding iAds to an application in an afternoon
Tuesday, July 13, 2010 During his WWDC10 keynote Steve Jobs mentions that developers can add iAds to an existing application “in an afternoon”. This does indeed seem to be the case but I found some of the documentation to be unclear so here are my notes on integrating iAds with a typical table based app.
Assumptions
I am assuming the Base SDK is set to iPhone Device 4.0 and also that the iPhone OS Deployment Target is set to iPhone OS 3.1 so that we need to maintain backward compatibility with older devices. There is not much choice for the Base SDK setting as all new App Store submissions must be with iOS 4. The minimum deployment target is up to you and I will try to highlight the sections that are specifically aimed at backward compatibility.

The existing App
To best understand the process I will start with an existing app that I made using the tab bar template in Xcode. The app contains two tab bar items both of which contain UINavigationControllers. The first navigation controller also contains a UITableViewController which when a row is selected drills down into a detailed view controller. The basic appearance of the app when the first tab bar item (“Top rated”) is selected to show the table view is as follows:

The detailed view which is pushed onto the navigation controller stack when a table row is selected simply shows the row number in a text label as follows:

The main NIB file for this app is as follows:

The table view controller is named FirstViewController and has its view loaded from a Nib file (FirstView.xib) which looks like this:

The definition for FirstViewController is trivial at this point as it simply implements a UITableViewController:
@interface FirstViewController : UITableViewController {
}
@end
This is all pretty standard stuff but I hope it will make what is to come easier to follow.
Adding the framework
The iAd framework is new in iOS 4 so the first thing we need to do is include the framework in the application. Right-click on the Frameworks group in the Xcode project and select Add -> Existing Frameworks… and select the iAd.framework.

If you are just targeting iOS 4 devices this will work fine but if you want the app to also work on older versions of the OS this will cause a problem. Since the iAd framework is not available with those versions of the OS you will get a runtime error similar to the one below:
dyld: Library not loaded: /System/Library/Frameworks/iAd.framework/iAd
This error is generated because when you add a framework to an application it is set as Required by default. This means that is has to be present when the product loads which is not possible on pre iOS 4 systems. The only way to avoid this is to weak link the iAd framework which relaxes this restriction. Of course if we make any reference to the missing framework on an earlier version of the OS we will still get a runtime error leading to an application crash. To change the iAd framework to be weak linked right-click on the application target and from the General tab of the Info dialog change the Type for iAd.framework from Required to Weak.

Making space for the banner view
The iAd Framework is very simple and there is really only one class that we need to worry about. The ADBannerView needs to be added to the view hierarchy and connected to a view controller to display Ads when they become available.
The key to adding the ADBannerView to a table view is to ensure there is a container view for the table and then ensuring that the resize masks for the table view are set correctly. The reason we need a container view is that we want the ADBannerView and the UITableView to be at the same level in the view hierarchy.
To add the additional banner view we need to first change our table view controller (FirstViewController) as it currently expects to directly load a table view from its Nib file. To get around this we need to introduce a containing UIView and make the table view a subview of this new view. This makes the Nib file look as follows:

We also now need to adjust the definition for FirstViewController as it will now no longer have its view set directly to the UITableView. This means that the FirstViewController cannot now directly implement UITableViewController but must instead implement UIViewController and maintain a separate reference to the table view as follows:
@interface FirstViewController : UIViewController {
UITableView *tv;
}
@property (nonatomic, retain) IBOutlet UITableView *tv;
@end
The view controller implementation (FirstViewController.m) contains the corresponding sythesize and release code:
@implementation FirstViewController
@synthesize tv;
- (void)viewDidUnload {
[super viewDidUnload];
self.tv = nil;
}
- (void)dealloc {
[tv release];
[super dealloc];
}
The Nib file, after rewiring the view connections now looks like this (note how the view outlet is now connected to the new container View instead of the Table View):

There is one additional thing we need to take care of when changing the way our table view controller works. Since our view controller no longer directly sub-classes UITableViewController we lose the default viewWillAppear behaviour that clears table row selection each time the table is displayed. We need to add this same functionality into our own viewWillAppear:
- (void)viewWillAppear:(BOOL)animated {
NSIndexPath *indexPath = [self.tv indexPathForSelectedRow];
if (indexPath) {
[self.tv deselectRowAtIndexPath:indexPath animated:YES];
}
}
Understanding how to rearrange the view hierarchy to make space for the banner view is probably the hardest part of integrating iAds. This technique for introducing a container view for a table is actually also very useful in other situations where, for example, you want to show a toolbar alongside your table.
Check your autosizing masks
Before moving on there is one important tip that I need to point out here that caused me to waste some time. Since we will be inserting and removing our Ad Banner View into the view hierarchy we need to make sure that our table view resizes correctly. If you do not get this step right you will most likely never see the Ad Banner as it will be hidden behind the table view.
When you add a table view in Interface builder it sets things up assuming that the table view will occupy the entire view. You can see this in the inspector if you look at the autosizing masks:

In this example I intend to place the Ad Banner View at the bottom of the screen, just above the tab bar. So rather than having a fixed strut at the bottom of the view the space is flexible. That way the table height will expand or contract to fill the available space.

Creating the Banner View
At this point if I was building an app only for iOS 4 I would probably just drag an Ad BannerView object from the Interface Builder library and drop it under my new view and be done. However since I want to maintain backwards compatibility I cannot place the ADBannerView object directly into the Nib file. If I do that I will get a runtime error when the Nib file is loaded on a pre iOS 4 version of the system. Instead we will need to create the ADBannerView object in code.
First we will make some changes to the view controller interface (FirstViewController.h):
- Import the iAd.h header file
- Set the ADBannerViewDelegate
- Retain a reference to the ADBannerView that we will create
The resulting header file now looks like this:
#import <UIKit/UIKit.h>
#import <iAd/iAd.h>
@interface FirstViewController : UIViewController <ADBannerViewDelegate> {
UITableView *tv;
ADBannerView *bannerView;
}
@property (nonatomic, retain) IBOutlet UITableView *tv;
@property (nonatomic, retain) ADBannerView *bannerView;
@end
In the controller implementation we synthesize the bannerView and make sure we also release it with the controller. There is an important extra step we need to take of when releasing the bannerView which is to ensure we also remove the banner view delegate. Failure to remove the delegate can cause an app crash if the iAd framework calls it after the view controller has been released. We will see where the delegate gets set shortly but for now we will modify the viewDidUnload and dealloc methods to ensure we clean up correctly:
@implementation FirstViewController
@synthesize tv, bannerView;
- (void)viewDidUnload {
[super viewDidUnload];
if (self.bannerView) {
bannerView.delegate = nil;
self.bannerView = nil;
}
self.tv = nil;
}
- (void)dealloc {
if (bannerView) {
bannerView.delegate = nil;
[bannerView release];
}
[tv release];
[super dealloc];
}
- (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 initial 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];
}
}
Some additional comments:
- to maintain backward compatibility with iOS 3.x versions we cannot call [ADBannerView alloc] directly. To work around this use the NSClassFromString method to get a reference to the class and test if it exists. When createBannerView is called on iOS 3.x it simply returns without doing anything.
- the ADBannerView properties requiredContentSizeIdentifiers and currentContentSizeIdentifier set the possible sizes of the banner view and then the current size of the banner view respectively. There are currently only two possible sizes 320x50 for portrait mode and 480x32 for landscape mode. We start off in portrait mode but will handle rotation shortly.
- We set the delegate of the ADBannerView to be this view controller.
- We set the autoresizing mask of the banner view to have flexible top and right margins so that it resized correctly when it rotates. If you were placing the banner view at the top of view you would replace the flexible top with a flexible bottom margin.
- We start with the banner view positioned off-screen so that it is hidden from the user. We will then implement delegate methods that only move the view onscreen when the iAd framework tells us it has an Ad to display.
- Finally we retain a reference to the banner view, add it to our view hierarchy and release the local reference.
- (void)viewDidLoad {
[self createBannerView];
}
Showing the banner
To display the banner we need to do two things:
- reduce the size of the table view frame to make space for the banner view. The height of the table view frame needs to be reduced by the height of the banner view.
- move the origin of the banner view frame so that it now onscreen in the space left by the table view. The y coordinate of the banner frame is set to the full height of the view frame (that is the container view not the table view) minus the height of the banner view. This ensures that the banner view is lower than the table view but above any tab bar at the bottom of the screen.
- (void)showBanner {
CGFloat fullViewHeight = self.view.frame.size.height;
CGRect tableFrame = self.tv.frame;
CGRect bannerFrame = self.bannerView.frame;
// Shrink the tableview to create space for banner
tableFrame.size.height = fullViewHeight - bannerFrame.size.height;
// Move banner onscreen
bannerFrame.origin.y = fullViewHeight - bannerFrame.size.height;
[UIView beginAnimations:@"showBanner" context:NULL];
self.tv.frame = tableFrame;
self.bannerView.frame = bannerFrame;
[UIView commitAnimations];
}
Hiding the banner
To hide the banner is the reverse of showing the banner:
- increase the size of the table view frame to fill the container view with the table. The height of the table frame is set to the full height of the container view frame.
- move the origin of the banner view so that it is now offscreen. The y coordinate of the banner view frame is set to the full height of the container view.
- (void)hideBanner {
// Grow the tableview to occupy space left by banner
CGFloat fullViewHeight = self.view.frame.size.height;
CGRect tableFrame = self.tv.frame;
tableFrame.size.height = fullViewHeight;
// Move the banner view offscreen
CGRect bannerFrame = self.bannerView.frame;
bannerFrame.origin.y = fullViewHeight;
self.tv.frame = tableFrame;
self.bannerView.frame = bannerFrame;
}
Implementing the ADBannerView delegate methods
We use the ADBannerView delegate methods to actually invoke the showBanner and hideBanner methods. There is no guarantee that there will always be an Ad to display. In fact at time of writing iAds are only available in the US. To ensure that we do not display a blank space when there is no Ad available we rely on the delegate method bannerViewDidLoadAd to tell us when an ad is available. Likewise when the iAd framework no longer has an Ad to display it will call the bannerView:didFailToReceiveAdWithError delegate method so we should hide the view:
- (void)bannerViewDidLoadAd:(ADBannerView *)banner {
[self showBanner];
}
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error {
[self hideBanner];
}
Handling rotation
The final thing we need to handle is device rotation. To change the size of the banner view we just need to set the currentContentSizeIdentifier property of the bannerView to the correct constant based on the device orientation. We will first create a helper method:
- (void)changeBannerOrientation:(UIInterfaceOrientation)toOrientation {
if (UIInterfaceOrientationIsLandscape(toOrientation)) {
self.bannerView.currentContentSizeIdentifier =
ADBannerContentSizeIdentifier480x32;
}
else {
self.bannerView.currentContentSizeIdentifier =
ADBannerContentSizeIdentifier320x50;
}
}
Then in addition to ensuring that shouldAutorotateToInterfaceOrientation allows the view to rotate we also need to implement willRotateToInterfaceOrientation to fix the banner view by calling our helper method:
- (BOOL)shouldAutorotateToInterfaceOrientation:
(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
- (void)willRotateToInterfaceOrientation:
(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration {
if (bannerView) {
[self changeBannerOrientation:toInterfaceOrientation];
}
}
Almost done
We are just about done. If we take a look at the app now in the simulator we should see a test Ad slide into view a few seconds after the app starts. The Ad should slide to the left along with that table when we select a row in the table. Also rotating the device should cause the banner view to rotate and resize correctly:


One final problem
There is one final problem with the banner view not resizing correctly in certain situations. This happens if the device is rotated when our view controller (FirstViewController) is not actually handling the view. So for example if we select a row in the table to drill down to the detail view and then rotate the device. Since in this situation the willRotateToInterfaceOrientation method we added to the controller will not be called we do not get a chance to resize the banner view.
To fix this situation we get the current device orientation in viewWillAppear and use it to call our helper method to set the banner size based on the orientation. The modified viewWillAppear method now looks like this:
- (void)viewWillAppear:(BOOL)animated {
NSIndexPath *indexPath = [self.tv indexPathForSelectedRow];
if (indexPath) {
[self.tv deselectRowAtIndexPath:indexPath animated:YES];
}
if (bannerView) {
UIInterfaceOrientation orientation = self.interfaceOrientation;
[self changeBannerOrientation:orientation];
}
}
All Done
This may well be the longest post that I have made on this site so I will leave it to you to decide if Steve was right about doing this in an afternoon. I think once you have seen what to do it is fairly quick but there are a couple of tricks to learn to make it go smoothly. Anyway I hope it was useful and if I missed something by all means let me know in the comments.
Keith
I have modified the orientation code in viewWillAppear following a suggestion from Fred in the comments. Instead of calling [[UIDevice currentDevice] orientation] it now uses self.interfaceOrientation to get the orientation directly from the view controller. See the comments for other suggestions.
Keith
I have had several requests for the source code for the example project used in this post. There is very little code that I did not already show but if it helps I have posted the Xcode project here for download.
Keith
There was a typo in the hideBanner method. The offending line was:
self.view.frame = bannerFrame;
This should be:
self.bannerView.frame = bannerFrame;
37 Comments | tagged
iAd
Reader Comments (37)
Thank you Keith for this great tutorial. I never had time to look through the iAd documentation and your article puts it nicely in context. The clean Apple implementation is a real relief when you look at the way third-party ads frameworks behave, especially on the iPad and interface orientation front.
I really like the way your code looks, and have a few code-style questions:
Why do you use properties all around in your code but use the ivar directly in the dealloc method?
Why don't you call viewDidUnload in your dealloc method as both share the same code?
Then there's the topic of interface orientation changes not being propagated to non-visible view controllers which I find a bit silly. Why do you use [[UIDevice currentDevice] orientation] instead of self.interfaceOrientation to determine orientation on viewWillAppear? Isn't self.interfaceOrientation correctly updated? Do you know when it will be set correctly after you get back to the view controller?
Thanks for your time!
Fred
Fred thanks for the great feedback. I wish I could get you to review all of my code :-)
Some responses to your questions:
I know that I am not always 100% consistent in my use of properties/ivars but in the dealloc method I think it is generally the recommendation from Apple to access the variable directly rather than going via the setter.
I don't really like the idea of calling viewDidUnload from dealloc what I think would be better is to refactor the common code into a new method and call that from both viewDidUnload and dealloc. Either way I agree there is currently some unnecessary and ugly code repetition.
For the interface orientation change I have to be honest and say I forgot about the interfaceOrientation property of UIViewController. I agree using self.interfaceOrientation in place of [UIDevice currentDevice] orientation] is much nicer and I should probably update the code to show that.
Thanks again for the excellent feedback!
I don't think it takes one afternoon. What if I have 6-7 UITableView's? Then it becomes a nightmare. There should have been a cleaner way to it! Actually it's about the interface system of iPhone. It's not flexible as HTML or Android's interface system.
One question I would have if you have 6-7 UITableViews is if you would really want to put an Ad on every single one? That maximises the Ad impressions for sure but at the expense of the user experience. I have not seen any guidelines from Apple but my feeling is that you would want to limit the number of places where you show the ADBannerView. If you are showing it in several different view controllers you may also want to look at sharing the ADBannerView across all of them to avoid recreating it each time.
Thanks for the tutorial.
I would like to place iAds near the bottom of the screen above a tab much like you have done. However I have a search functionality where a keyboard animates up from the bottom of the screen which would cover the advert. Do you think Apple would reject an app constructed like this?
@easinewe I don't see anything in the iAd Network agreement that says you cannot cover an iAd with the keyboard during user input. I don't see that as being any different from moving the Ad banner offscreen to hide it. You may want to hide the ADBannerView whilst the keyboard is displayed and then bring it back when the keyboard is dismissed. Of course what Apple may decide to reject is difficult for anyone to say - unfortunately you will have to submit your app to find out for sure.
This is the only iAd tutorial I've come across that really works, excellent job!
Thanks Keith for the great tutorial.
I wonder if you have any ideas as to why there is a memory leak in both Apple's version and your version of iAd implementation.
My app's have no memory leaks without the iAd framework. I first followed Apple's WWDC iAd video and it worked ok but they neglected to provide support for iOS3 devices, no good. It also resulted in one new memory leak which I attempted to fix to no avail.
Seeking info regarding iAd and backward compatibility lead me to your great tutorial. Great! I though, perhaps I can kill two birds here. Not the case, same leak after more hours debugging. So finally today I downloaded your provided source to get to the root of this.
I just ran it on the device with Instruments and there it is again.
My instruments debugging skills are limited to finding my own silly release mistakes right now, anything that goes into Foundation and other Apple frameworks as a result is a little beyond me if you know what I mean.
Any thoughts?
Leak * 6:
NSCFString 16bytes, Foundation, -[NSCFString copyWithZone:]
Regards
Dave.
@Dave: thanks for the feedback. There are several reports in the Apple forums of leaks similar to the one you mention. The strange thing is that testing on a device running iOS 4 I am not able to reproduce it. In the end if you find a bug in an Apple framework the only thing you can do is file a bug report with Apple and try and find a workaround until they fix it.
Thanks for getting back so quick Keith. Running iOS4 here too, so the plot thickens. I will keep banging away at it. Again, great post. Does exactly what it's supposed to do, tested it on three devices running iOS all the way back to 3.0. Cheers.
@Dave: no problem, one thing I should mention in case you did not see it is that I updated the example code today to remove a typo in the hideBanner method. (corrected line is self.bannerView.frame = bannerFrame;).
Hi there,
The tutorial is great but there is one problem with the code when run with iOS 4 on iPhone 3Gs and 4 and fast app switching is used:
Connect your iPhone to the Internet.
Now open the App. Ad-Banner is shown. Now click the home button and switch your phone to airplane mode.
Now click your app-icon again: It will resume where left off and still show the banner!
So in the app-delegate method
- (void)applicationDidEnterBackground:(UIApplication *)application
you should add calls to hideBanner & releaseBanner methods of your viewController class and in
- (void)applicationDidBecomeActive:(UIApplication *)application
the viewControllers createBanner method should be called.
Andrew
Great catch Andrew - I will update the example app when I get a few minutes.
How to display banner on top of the table view ?I mean below the header view
@Tom to place the banner view at the top you need to make changes in a few places:
1) in the NIB file change the autosizing mask for the table view so that the flexible strut is at the top
2) in the createBannerView method change the autosizing mask for the bannerView to have a flexible bottom margin in place of a flexible top:
adView.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin;
3) also in createBannerView set the origin of the banner view so that is hidden above the top of the table view frame. To do this set the y-coord to minus the size of the bannerView.
bannerFrame.origin.y = - bannerFrame.size.height;
4) in showBanner modify the code to move the table view down and the banner view on screen. The origin of the table view frame needs to move down by the size of the banner view and the origin of the banner view frame is set to zero so that it is at the top of the frame:
tableFrame.origin.y = bannerFrame.size.height;
...
bannerFrame.origin.y = 0;
5) in hideBanner modify the code to move the table view up and move the banner view off screen. The table view origin is now zero so that it as the top of the view and the banner view is again moved above the frame:
tableFrame.origin.y = 0;
bannerFrame.origin.y = - bannerFrame.size.height;
Just wanted to say thanks for this tutorial. With its help, it took me about 4 hours to get iAds properly working in my app. Actually, a good portion of that time was dealing with specific layout problems in my app which differed from this example, but in the end it worked out and it is actually quite a simple process.
Couldn't have done it as easily without this example though. Thanks.
@andrewhoyer
Thank you for this brilliant tutorial.
I've a question (I'm still a newbie for iphone development).
My question is about the changed the FirstViewController to subclass from UITableViewController to UIViewController, you also added the declaration of tv as UIViewTableController.
I did the same in a Navigation Based Application and it worked fine!
However the method
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
I had to handle the selection of a row is no more called. How can I do that? Am I missing something?
Moreover I am surprise that other methods like
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
that are part of the UITableViewController are still called. If tv now is a property of the FirstViewController instance, how is this possible? And if this is working, why the other method for the selection of a row is not working?
Ok, actually I've just another question: you change the viewWillAppear adding two lines. Can you please explain these better? I understood the reason (it's the same reason I said below, the FirstViewController is not directly subclassing the UITableViewController) but I don't really get what these two lines are used for:
- (void)viewWillAppear:(BOOL)animated {
NSIndexPath *indexPath = [self.tv indexPathForSelectedRow];
if (indexPath) {
[self.tv deselectRowAtIndexPath:indexPath animated:YES];
}
//[super viewWillAppear:animated];
}
Thank you so much,
Emanuele
Ok,
I've fixed the problem. The problem was I forgot to set the TableView as delegate for the file's owner. Which also answer the answer how the FirstViewController can handle methods for the TableView.... simply because it's a delegate...
Still some confusion to me, but that's working now.
Thank you once more.
Emanuele
Hi Emanuele, thanks for the feedback. I am glad you got it working.
The change to viewWillAppear is to reproduce behaviour that is normally provided for free when you directly subclass UITableViewController. What the additional lines of code do is check if a row in the table is selected and if so deselect the row using an animated fade. The indexPathForSelectedRow method returns the row and section of the selected row (if any) which is then used to deselect the row.
This is typical in a navigation based app when you are returning to a table view after having made a selection. It allows the user to briefly see the row they had previously selected before the selection fades away. Try commenting out these lines and you will see the difference when you navigate back from the detailed view.
First off - awesome tutorial! This answered my questions exactly.
I did run into one problem inside of my code. I have an edit button on the navigation bar
After implementing this code, the edit button does not do anything. The tableview no longer changes to show the red minus icons. On the contrary, if I perform a swipe over a table cell to reveal the delete button, I can still delete that cell.
Any ideas on how to get the edit button working again?
@Brian, thanks for the feedback. I would guess the problem was introduced because your view controller no longer inherits from the UITableViewControllerClass. Try checking the connections of the UITableView object in the view NIB file. It should have both its data source and delegate set to the view controller.
I have a simple project that is based upon a UITableViewController and I've followed through your example but I seem to have missed something :(
My ads scroll with my table - they arent fixed at the bottom!!!
What have a missed? I'm fairly new to xcode and I'm sure i've not done something with the new view you inserted into the hierarchy.
@Ryan - sounds like you are adding the ADBannerView to the wrong view. You should have a plain UIView (self.view in my example) which contains the UITableView (self.tv in my example).
When you create the ADBannerView you should add it to the containing view not the UITableView:
[self.view addSubview:adView];
Hope it helps
Thank you for a great tutorial. Have been struggling on and off for a couple of days to get this working correctly. Setting the autosizing properties as described did the trick.
Am I misunderstanding the memory used here?
Every time the view loads... you alloc memory for the iAd, save a copy of it, add it to the view, then release it.
Every time the view unloads... you set iAds pointer to nil. (and never free it) But it's now nil and can never be free'ed or used.
When the view reloads... you alloc MORE memory for the iAds, save another copy of it, add it to the view again, then release it.
No?