Thứ Ba, 31 tháng 8, 2010

SQLite-Adding,Deleting,Updating

SQLite Tutorial - Adding data


Inserting data into SQLite database is a breeze with iPhone applications. In the third part of SQLite tutorials, I will show how to insert data into the database.
Introduction

Adding data into the database is very simple using the SQLite library, although there are some tweaks we have to perform if we want the application to behave in a certain way. This tutorial picks up its source code from the last tutorial in this series. This is how the "AddView" looks like

The work flow is as following, a user will click on the "Add" button on the left hand bar button item and the "AddView" will be presented to him. The user will enter the coffee name and price and click on "Save" on the navigation item on the right hand side to save it. Internally, it will call "save_Clicked" method which will create a "Coffee" object and set all the right properties. We then call "add_Coffee" method on "SQLAppDelegate" which will call "add_Coffee" method on the "Coffee" class and then it will add the object to the array. We then dismiss the add view controller and reload data on the table view in "viewWillAppear" method implemented in "RootViewController.m".

Creating the addCoffee Method
First thing we do is create the addCoffee method in the Coffee class which is responsible for inserting data in the database.

This is how the header file looks like (Complete code not shown)

- (void) addCoffee;

and the method is implemented in Coffee.m file

- (void) addCoffee {

if(addStmt == nil) {
const char *sql = "insert into Coffee(CoffeeName, Price) Values(?, ?)";
if(sqlite3_prepare_v2(database, sql, -1, &addStmt, NULL) != SQLITE_OK)
NSAssert1(0, @"Error while creating add statement. '%s'", sqlite3_errmsg(database));
}

sqlite3_bind_text(addStmt, 1, [coffeeName UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_bind_double(addStmt, 2, [price doubleValue]);

if(SQLITE_DONE != sqlite3_step(addStmt))
NSAssert1(0, @"Error while inserting data. '%s'", sqlite3_errmsg(database));
else
//SQLite provides a method to get the last primary key inserted by using sqlite3_last_insert_rowid
coffeeID = sqlite3_last_insert_rowid(database);

//Reset the add statement.
sqlite3_reset(addStmt);
}

The "add_Stmt" is declared as static sqlite3_stmt variable. It is finalized in finalizeStatements and this is how the code looks like

//Complete code listing not shown
#import "Coffee.h"

static sqlite3 *database = nil;
static sqlite3_stmt *deleteStmt = nil;
static sqlite3_stmt *addStmt = nil;

@implementation Coffee
...


+ (void) finalizeStatements {

if(database) sqlite3_close(database);
if(deleteStmt) sqlite3_finalize(deleteStmt);
if(addStmt) sqlite3_finalize(addStmt);
}

Coming back to addCoffee method, "add_Stmt" is built using the appropriate insert SQL code. To bind the coffee name the following method sqlite3_bind_text is used and sqlite3_bind_double is used to bind the price variable to the insert statement. Since the method only accepts a value of datatype double, we send doubleValue message to the receiver. Execute the statement using sqlite_step method and if it returns SQLITE_DONE then the row was successfully added to the database. We still do not have the primary key for the row which was inserted, which we can get by calling sqlite3_last_insert_rowid method and passing the database object. The "rowid" is only returned on column of type INTEGER PRIMARY KEY.

After adding the data in the database, we have to add the coffee object to the coffeeArray declared in SQLAppDelegate class. To do this we will declare a method called "add_Coffee" which will take a parameter of type Coffee and this method is called from "save_Clicked" method, which is called when the user clicks on the save button.

This is how the header file changes (Full code not shown)
//FileName: SQLAppDelegate.h
- (void) addCoffee:(Coffee *)coffeeObj;
...

addCoffee is implemented in SQLAppDelegate.m file and this is the code listing

- (void) addCoffee:(Coffee *)coffeeObj {

//Add it to the database.
[coffeeObj addCoffee];

//Add it to the coffee array.
[coffeeArray addObject:coffeeObj];
}

The first line calls the "addCoffee" method on the coffee object which we just created. The second line adds the object in the array.

Now we have to work with the Add View and its associated view controller.

Adding new UIView
Create a new view using the Interface Builder, I have named the view "AddView". Add two labels and two text boxes as shown in the figure below. For the text fields, Capitalize is set to "Words", Return Key is set to "Done" and set the Placeholder as "Coffee Name" and "Price" for the two respective text fields. You would set the properties in "Text Field Attributes" (Tools -> Inspector) using IB. Open "Connections Inspector" and create a connection from the delegate property to "File's Owner" object, do the same for both the text boxes. I find it hard to explain what goes in IB, so here is a screen shot of how it should look like. The "Text Field Connections" applies to both the text boxes.



Creating a UIViewController
Create a new view controller (using Xcode), the name of my file is "AddViewController". Create two variables of type UITextField with "IBOutlet" attribute so the variables show up in IB and create two methods called "save_Clicked" and "cancel_Clicked". This is how the header file should look like

#import

@class Coffee;

@interface AddViewController : UIViewController {

IBOutlet UITextField *txtCoffeeName;
IBOutlet UITextField *txtPrice;
}

@end

Now that we have defined "AddViewController" set the File's Owner class as "AddViewController" in "Controller Identity", below is a screen shot of that.

Also link the text fields declared in the view controller to the objects on the "AddView", below is a screen shot of that

We are done with using Interface builder, feels good now that we can concentrate on code.

We now have to add two buttons "Cancel" and "Save" on the "UINavigationItem" of the "AddView". Let us do this in "viewDidLoad" method and this how the code looks like

- (void)viewDidLoad {
[super viewDidLoad];

self.title = @"Add Coffee";

self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self action:@selector(cancel_Clicked:)] autorelease];

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemSave
target:self action:@selector(save_Clicked:)] autorelease];

self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];
}

Everything is self explanatory, we set the title, add two buttons and set the background of the view by passing groupTableViewBackgroundColor message to UIColor.

Since the view has only two text fields, it will be easier if the keypad is presented to the user when the view is loaded. Lets do this in "viewWillAppear" method and this is how the code looks like

- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];

//Set the textboxes to empty string.
txtCoffeeName.text = @"";
txtPrice.text = @"";

//Make the coffe name textfield to be the first responder.
[txtCoffeeName becomeFirstResponder];
}

Here we always set the text property of the text fields to empty string and we make the keypad show up for the coffeeName text field by passing becomeFirstResponder message.

Since the user can click "Done" the keyboard should be hidden and the method which gets called is textFieldShouldReturn and this is how the code looks like

- (BOOL)textFieldShouldReturn:(UITextField *)theTextField {

[theTextField resignFirstResponder];
return YES;
}

Please note that, I send "resignFirstResponder" message to the text field without finding out which textbox should be hidden. I do this because, the actual save happens in "save_clicked" method.

Before we look at "save_Clicked" method, this is how "cancel_Clicked" method looks like

- (void) cancel_Clicked:(id)sender {

//Dismiss the controller.
[self.navigationController dismissModalViewControllerAnimated:YES];
}

and this is how the "save_Clicked" method looks like

- (void) save_Clicked:(id)sender {

SQLAppDelegate *appDelegate = (SQLAppDelegate *)[[UIApplication sharedApplication] delegate];

//Create a Coffee Object.
Coffee *coffeeObj = [[Coffee alloc] initWithPrimaryKey:0];
coffeeObj.coffeeName = txtCoffeeName.text;
NSDecimalNumber *temp = [[NSDecimalNumber alloc] initWithString:txtPrice.text];
coffeeObj.price = temp;
[temp release];
coffeeObj.isDirty = NO;
coffeeObj.isDetailViewHydrated = YES;

//Add the object
[appDelegate addCoffee:coffeeObj];

//Dismiss the controller.
[self.navigationController dismissModalViewControllerAnimated:YES];
}

We create a new coffee class and set all the properties, isDetailViewHydrated is set as YES because all the data is in memory and isDirty is set as NO because the row will be inserted in the database after setting all the properties. The view is dismissed by passing "dismissModalViewControllerAnimated" message to the receiver.

We still have to add the "Add" button to the "RootViewController" and add the code to show the "AddView". This is how the header file changes for "RootViewController"

#import

@class Coffee, AddViewController;

@interface RootViewController : UITableViewController {

SQLAppDelegate *appDelegate;
AddViewController *avController;
UINavigationController *addNavigationController;
}

@end

Do not forget to import "AddViewController.h" in "RootViewController.m" file. The "Add" button is added in the "viewDidLoad" method and this is how the code changes

- (void)viewDidLoad {
[super viewDidLoad];

self.navigationItem.rightBarButtonItem = self.editButtonItem;

self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self action:@selector(add_Clicked:)];

appDelegate = (SQLAppDelegate *)[[UIApplication sharedApplication] delegate];

self.title = @"Coffee List";
}

When "Add" is clicked "add_Clicked" method is called and this is how the code looks like

- (void) add_Clicked:(id)sender {

if(avController == nil)
avController = [[AddViewController alloc] initWithNibName:@"AddView" bundle:nil];

if(addNavigationController == nil)
addNavigationController = [[UINavigationController alloc] initWithRootViewController:avController];

[self.navigationController presentModalViewController:addNavigationController animated:YES];
}

The reason we present the "AddView" using "addNavigationController" because when we want a custom UINavigationItem to show up on the AddView and that is why we initialize "addNavigationController" with "avController" and present it using "presentModalViewController".

Run your application to insert data in the database.

There is however a problem with the design here, a User can click on Edit and nothing restricts him/her from clicking the add button. The code below fixes the issue

- (void)setEditing:(BOOL)editing animated:(BOOL)animated {

[super setEditing:editing animated:animated];
[self.tableView setEditing:editing animated:YES];

//Do not let the user add if the app is in edit mode.
if(editing)
self.navigationItem.leftBarButtonItem.enabled = NO;
else
self.navigationItem.leftBarButtonItem.enabled = YES;
}

The method setEditing is called when the edit button is clicked. We send the same message to the parent class and the tableview, also disable or enable the leftBarButtonItem if the tableview is in edit mode.

Conclusion
As we can see inserting data in SQLite databases is very easy to do.

Happy Programming,
iPhone SDK Articles



Attachments
Suggested Readings

SQLite-ToDo List

Creating a ToDo List Using SQLite Part 1

If you're new here, you may want to subscribe to my RSS feed. Thanks for visiting!

If you have been following my tutorials, you know that we have been working primarily with UITableViews. This is mostly because SO many applications can be developed using this simple control. This final UITableView tutorial will be taking all of the skills learned from previous tutorials, putting them all together, and adding SQLite to create a prioritized To-Do list. I will also be showing you how to add multiple columns to your table cells and we will be exploring some of the other controls that the iPhone has to offer. What good would the tutorials be if we didn’t use them to create something useful.

I will move a little faster in this tutorial while still explaining the new stuff in detail. I will assume that you have completed the fruits tutorial and it’s prerequisites.

This tutorial will be a multipart series as it will be a little longer than my previous ones. In this first tutorial, you will learn:

So let’s get started…

Open up X-Code and Select File->New Project… Select Navigation-Based Application and click Choose…

Name your project todo. Now let’s create the todo database that we will be using. Open up the Terminal application on your Mac. This is located in Applications > Utilities.

If you have installed XCode, you should have mysqlite3 already on your computer. To check this, type:

sqlite3 into the Terminal and sqlite3 should start. Type .quit to exit. If sqlite3 is not installed, install all of the XTools from your Mac Installation Disk.

Now that the terminal is open let’s create the database. This is done with the command:

sqlite3 todo.sqlite

SQLite3 will now start and load the todo.sqlite database. By default the database is empty and contains no tables. If you need a refresher on the basics of SQL databases Google It. Since our application is fairly simple, we only need to create one table. We will create a table called todo by typing the following statement:

CREATE TABLE todo(pk INTEGER PRIMARY KEY, text VARCHAR(25), priority INTEGER, complete BOOLEAN);

One thing to note here is the pk field. It is the primary key of the table. This adds functionality such that every time a row is added to the database, it auto-increments this field. This will be a unique identifier to identify each row. All of the other fields should be fairly self explanitory.

Now that our table has been created, let’s add some data. We will eventually be adding todo items within our app, but for now we will add some defaults. Type the following commands below.

INSERT INTO todo(text,priority,complete) VALUES('Take out the trash',3,0);
INSERT INTO todo(text,priority,complete) VALUES('Do Computer Science homework',1,0);
INSERT INTO todo(text,priority,complete) VALUES('Learn Objective C',1,0);
INSERT INTO todo(text,priority,complete) VALUES('DIGG this tutorial',2,0);

You can add as many todo items as you would like. For this tutorial, make sure you enter a priority between 1 and 3 (You’ll see why later). Now our database has been created and populated let’s exit out of SQLite3. Do this by typing .quit. Your terminal window should look something like this.

Now go back to XCode. Do a Control-Click (right click) on the folder named Resources. Click Add -> Existing Files… and browse to your todo.sqlite file and click Add. It will then prompt you with a screen like this.

Make sure you check the box that says Copy items into destination group’s folder (if needed). You should now see the todo.sqlite file inside of the resource folder.

Now that we have added the database, we need to load the Objective C libraries so we can use it. Do a control-click (right click) on the Frameworks folder. Click Add -> Existing Frameworks. Now this part is a little strange. It has been my experience that these libraries are not in the same place on all machines. So in the search bar type in libsqlite3. The file we are looking for is called libsqlite3.0.dylib. This may pull up multiple files as OSX has it’s own versions of this file. Just click on the largest of the files that show up and click Add. As you can see, mine is about 1.7 MB.

Now it should add the framework and your directory will look something like this:

We need to create an object to hold our todo information. We will eventually be making an array of these objects to populate a UITableView. Go ahead and click File -> New File… Select NSObject Subclass and click Next.

Name this object todo.m and check the box that says Also create “Todo.h” and click Finish.

Open up todo.h and add the following code.

We see some new things here…First, there is a variable of type sqlite3 called database. This will be a reference to the applications database and will allow the todo object to communicate with it. Make sure you add a #import in your imports.

Next, we see a primary key. Notice that in the property declaration it has the keywords assign and readonly. This tells the compiler that this variable, once assiged, can not be changed again. This is good since each todo will be uniquely identified by this variable.

Also, I have declared a method called initWithPrimaryKey. This will be the contstructor for this object. It takes an integer to assign as the primary key and an sqlite3 object to use as the database reference.

Let’s implement this method…Open up todo.m and add the following code.

Advertisement

There are quite a few new things that need to be explained here. I will just go through it line by line.

static sqlite3_stmt *init_statement = nil

This will hold our initialize statement when retrieving todo data from the database. This statement is static, meaning it is independent of any instance. In other words, there will be only one of them no matter how many todo objects we create. This statement will get compiled and allow us to do some neat things. I’ll explain more in a bit.

The next lines makes sure that the super class (NSObject) initilizes properly before we initilize a todo object. We then set the local primary key and database objects to the parameters passed to the initWithPrimaryKey method.

Now some interesting stuff happens. The next lines checks if our init_statment is null. This will happen only once per launch of the application. If it is null, we create a new string containing an SQL statement. If you are familiar with SQL at all, this should look pretty familiar with one exception. What is a question mark doing in there? Well, I will tell you. After the SQL statement gets compiled, we can bind a value to it that will eventually replace the question mark. So this allows us to have 1 generic SQL statement, but bind different values to it to retrieve different results. So the next line, you guessed it, prepares the statement and stores it in our init_statement. The if statement just checks to see if this finished correctly and prints an error if there was a problem.

Moving on… The line sqlite3_bind_int simply replaces that question mark with the primary key of the current todo object, so what we end up with is statements like this:

SELECT text FROM todo WHERE pk = 1;
SELECT text FROM todo WHERE pk = 2;
SELECT text FROM todo WHERE pk = 3;
SELECT text FROM todo WHERE pk = n;

After that, the sqlite3_step(init_statement) method is called. This method executes the SQL statement on the database. It is contained inside of an if statement to make sure it executed properly. Now we can finally access the todo data. We see this line:

self.text = [NSString stringWithUTF8String:(char*) sqlite3_column_text(init_statement,0)];

Wow, that’s a mouthful… Let’s analyze it. The sqlite3_column_text method tells SQL that we want to retrieve a string object from the database. It has 2 parameters. The first, is just a reference to the SQL statement that was used. The second is the column number that we wish to get text from. So in this case, we only have one column (SELECT text FROM…) so there is only 1 index and that’s the 0th index. Next, the (char *) is just a cast to a string (might not be needed, but good practice). And finally, we build an NSString object with the data returned so that we can assign self.text to it.

This is quite a bit to explain in just text. If I have lost you, feel free to ask me questions in the comments.

We are done with the todo object for now…

Go ahead and open up todoAppDelegate.h and add the following code.

This should look a little familiar with the exception of a few lines. Notice that I have created an NSMutableArray of todo objects. This will be (like the fruit example) an array to hold our todo items. We will eventually use this array to populate a UITableView. The only new lines here are the import of sqlite3.h and the sqlite3 *database line. Now let’s open up todoAppDelegate.m and add some code.

One new thing we see here is a private interface. We declared it here because it’s specific to this object so it does not need to be declared in the .h file. The 2 functions we will implement are createEditableCopyOfDatabaseIfNeeded and initializeDatabase. Much of the code for these has already been written for us inside of Apple’s SQLBooks tutorial. I will going through this code and explaining it the best that I can. Add the following code.

What this method is essentially doing is copying the database from your project folder to the documents folder on your iPhone

. This will only happen once as it first checks if the database already exists in the documents folder. I’m not going to go through this line by line as it is fairly self explanitory. Apple does a great job of naming functions and variables so that we can understand what is going on. If I get enough requests in the comments, I’ll do a line-by-line writup of this function.

The next function we will implement is initializeDatabase. Add the following code:

That’s a lot of text! Don’t worry it’s mostly comments. Let’s analyze this code…Some of it is very similar to the fruits example.

The first line creates and initializes a NSMutableArray. We then go on to set this array to our object’s todos array and release the temporary object.

The next 3 lines locate the database we created inside of the documents folder. Following that, the sqlite3_open line open’s the database so we can access its data. If the database opens correctly, we then proceed to retrieve todo items. The first line:

const char *sql = "SELECT pk FROM todo";

is an SQL statement that we will use to get all of the primary keys from the database. We then prepare the statement (as we did inside the todo.m file) only this time there is no “?” in the statement. That is because there is not condition for retrieving the primary keys. We are simply saying “give me all of the primary keys in the database”.

Now we see a while loop that is stepping through the SQL results. Every time we call sqlite3_step, the next result gets retrieved. The line:

int primaryKey = sqlite3_column_int(statement,0);

retrieves the primary key from each result. This is very similar to retrieving the text in the todo.m class only we use the sqlite3_column_int method instead of the sqlite3_column_text method. This is done for obvious reasons.

After we have the primary key, we create a new Todo object and call the initWithPrimaryKey constructor that we created. The primary key gets passed as well as a reference to the database. This allows the Todo object to essentially “look itself up” in the database. Finally, we add the newly created Todo object to our array of todos.

The last statement sqlite3_finalize clears the statement from memory and does some other cleanup.

The last part of this tutorial is calling these functions to create and initialize the database. So add the following code to applicationDidFinishLaunching:

We are simply calling these functions. You can now click Build and Go but your application won’t display anything! You might be quite frustrated that you completed this portion of the tutorial and have yet to see anything. Well, stay tuned! I will have the next portion of this tutorial up soon.

For you ambitious programmers you could move on. If you notice, at this point we are in a similar situation as the fruit tutorial. We have an Array of objects that will eventually populate a UITableView.

This tutorial will be a 4 part series and I will show you how to use a few more controls. We will be adding, editing, and deleting todo items. If you have any questions, please leave them in the comments. Also, if you get lost you can download the sample code here

Happy iCoding!

iPhone SDK Tutorial: Reading data from a SQLite Database

I see many people asking for SQLite tutorials around, and since I am using SQLite for the next part in the Advanced RSS Reader Tutorial, I thought I would write up a quick tutorial on using SQLite with the iPhone SDK.

1. Project Requirements

I suggest that you have at least a basic understanding of SQLite, writing SQL statements, the XCode interface and using the terminal in OSX. If you don’t know anything about any of these topics then this tutorial probably isn’t for you.

2. Creating our SQLite database for our tutorial

We first need to create a database for use with our application. For the purposes of this tutorial we will be building a database of animals along with a little information on them and a picture.

Fire up a new Terminal window and make a new folder to store the database in, here are the commands I ran

cd /Users/lookaflyingdonkey/Documents
mkdir SQLiteTutorial
cd SQLiteTutorial
sqlite3 AnimalDatabase.sql

You should now be at a “sqlite” command prompt, this is where we will be building our database structure and entering some test data.

For our example we need the name of the animal, a short description and a link to an image. Follow the commands below to create the table and to enter some sample data.

CREATE TABLE animals ( id INTEGER PRIMARY KEY, name VARCHAR(50), description TEXT, image VARCHAR(255) );

INSERT INTO animals (name, description, image) VALUES ('Elephant', 'The elephant is a very large animal that lives in Africa and Asia', 'http://dblog.com.au/wp-content/elephant.jpg');
INSERT INTO animals (name, description, image) VALUES ('Monkey', 'Monkies can be VERY naughty and often steal clothing from unsuspecting tourists', 'http://dblog.com.au/wp-content/monkey.jpg');
INSERT INTO animals (name, description, image) VALUES ('Galah', 'Galahs are a wonderful bird and they make a great pet (I should know, I have one)', 'http://dblog.com.au/wp-content/galah.jpg');
INSERT INTO animals (name, description, image) VALUES ('Kangaroo', 'Well I had to add the Kangaroo as they are the essence of the Australian image', 'http://dblog.com.au/wp-content/kangaroo.jpg');

The first command will create the table with the required structure and the next four will insert some test data for us to work with. To ensure that you have entered the data correctly you can execute “SELECT * FROM animals;” and see if it returns the items above. Once you are confident that everything had been created successfully you can leave the sqlite command line by typing “.quit”.

3. Creating our Project

Now that our database is all ready to go we need to setup our X-Code project.

Start off by creating a new “Navigation-Based Application”.

Give your Project a name, I called mine “SQLiteTutorial”.

Now set your screen layout to how you prefer it, I suggest making the window as large as possible, and making the code view as tall as possible by dragging the horizontal slider to the top. This will allow you the most room to move when building your application.

Now its time to create the required classes and views for our application, we will start off by making our views.

Right Click on the “Resources” folder in the left hand pane and click “Add File”, we want to create a new “View XIB” under the “User Interfaces” group.

We now need to give it a name, to stick the Apple’s naming conventions we are going to call it “AnimalViewController.xib”, Now Click “Finish”.

Now we need to create two classes, the first one will represent an animal, right click on the “Classes” folder in the left hand pane, click “Add > New File…”, choose the “NSObject subclass” template under the “Cocoa Touch Classes” group and name it “Animal”.

The second class will be for our AnimalsViewController, right click on the “Classes” folder in the left hand pane, click “Add > New File…”, choose the “UIViewController subclass” under the “Cocoa Touch Classes” group and name it “AnimalViewController”.

4. Adding SQLite Framework and our Animal Database

Now that we have created all of our views and classes it is time to start the real grunt work.

First off we need to include the SQLite libraries so our application can utilise them. To do this you will need to right click on the “Frameworks” folder in the left hand pane, then click on “Add > Existing Frameworks…”, then navigate to “/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS2.0.sdk/usr/lib/” and double click the “libsqlite3.0.dylib” file. A popup will appear, just click “Add” and the library will be added to your project.

We also need to add our database we created earlier to the Resources folder, to do this simply right click on the “Resources” folder, click “Add > Existing Files…”, navigate to the location you created the database in then double click on the AnimalDatabase.sql file. Another popup will appear, just click add.

All done with the importing, time to code!

5. The Coding begins!

We are going to start the coding by building our “Animal” object, every animal will have 3 properties, a name, a description and an image URL.

Open up the “Animal.h” file from the “Classes” folder and edit its contents to look like below,

#import 

@interface Animal : NSObject {
NSString *name;
NSString *description;
NSString *imageURL;
}

@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSString *description;
@property (nonatomic, retain) NSString *imageURL;

-(id)initWithName:(NSString *)n description:(NSString *)d url:(NSString *)u;

@end

Most of the above code should be pretty familiar to you, the only thing that may not be is the initWithName line, this line will allow us to create a new object with the required data, we could have used the default init function, but it will be easier for us to define our own.

Now we will actually have to implement the Animal Object, open up the “Animal.m” file and edit its contents to look like below:

#import "Animal.h"

@implementation Animal
@synthesize name, description, imageURL;

-(id)initWithName:(NSString *)n description:(NSString *)d url:(NSString *)u {
self.name = n;
self.description = d;
self.imageURL = u;
return self;
}
@end

The above code should be pretty easy to read as well, it basically stores the supplied data from the initWithName function and return the object (self).

Now its time to setup the Application delegate to access the database.

Open up the “SQLiteTutorialAppDelegate.h” and edit its contents to look like below:

#import 
#import // Import the SQLite database framework

@interface SQLiteTutorialAppDelegate : NSObject {

UIWindow *window;
UINavigationController *navigationController;

// Database variables
NSString *databaseName;
NSString *databasePath;

// Array to store the animal objects
NSMutableArray *animals;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
@property (nonatomic, retain) NSMutableArray *animals;

@end

What we are doing here is importing the SQLite database framework and creating some variables for storing the database details and an array of animal objects.

Now open up the “SQLiteTutorialAppDelegate.m” file and edit its contents to look like below:

#import "SQLiteTutorialAppDelegate.h"
#import "RootViewController.h"
#import "Animal.h" // Import the animal object header

@implementation SQLiteTutorialAppDelegate

@synthesize window;
@synthesize navigationController;
@synthesize animals; // Synthesize the aminals array

- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Setup some globals
databaseName = @"AnimalDatabase.sql";

// Get the path to the documents directory and append the databaseName
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
databasePath = [documentsDir stringByAppendingPathComponent:databaseName];

// Execute the "checkAndCreateDatabase" function
[self checkAndCreateDatabase];

// Query the database for all animal records and construct the "animals" array
[self readAnimalsFromDatabase];

// Configure and show the window
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}

- (void)applicationWillTerminate:(UIApplication *)application {
// Save data if appropriate
}

- (void)dealloc {
[animals release];
[navigationController release];
[window release];
[super dealloc];
}

-(void) checkAndCreateDatabase{
// Check if the SQL database has already been saved to the users phone, if not then copy it over
BOOL success;

// Create a FileManager object, we will use this to check the status
// of the database and to copy it over if required
NSFileManager *fileManager = [NSFileManager defaultManager];

// Check if the database has already been created in the users filesystem
success = [fileManager fileExistsAtPath:databasePath];

// If the database already exists then return without doing anything
if(success) return;

// If not then proceed to copy the database from the application to the users filesystem

// Get the path to the database in the application package
NSString *databasePathFromApp = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:databaseName];

// Copy the database from the package to the users filesystem
[fileManager copyItemAtPath:databasePathFromApp toPath:databasePath error:nil];

[fileManager release];
}

-(void) readAnimalsFromDatabase {
// Setup the database object
sqlite3 *database;

// Init the animals Array
animals = [[NSMutableArray alloc] init];

// Open the database from the users filessytem
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK) {
// Setup the SQL Statement and compile it for faster access
const char *sqlStatement = "select * from animals";
sqlite3_stmt *compiledStatement;
if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK) {
// Loop through the results and add them to the feeds array
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
// Read the data from the result row
NSString *aName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)];
NSString *aDescription = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 2)];
NSString *aImageUrl = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 3)];

// Create a new animal object with the data from the database
Animal *animal = [[Animal alloc] initWithName:aName description:aDescription url:aImageUrl];

// Add the animal object to the animals Array
[animals addObject:animal];

[animal release];
}
}
// Release the compiled statement from memory
sqlite3_finalize(compiledStatement);

}
sqlite3_close(database);

}

@end

Now I know that may look like a fair bit of code and it probably also looks quite scary! But really it is quite simple and I have tried to comment nearly every line to describe to you what the line does and why it is there.

The checkAndCreateDatabase function checks to see if we have already copied our database from the application bundle to the users filesystem (in their documents folder), if the database hasn’t already been created or it has been removed for some reason it will be recreated from the default database.

Next the readAnimalsFromDatabase function will make a connection to the database that is stored in the users documents folder, and then executes the SQL statement “SELECT * FROM animals”. It will then go through each row that is returned and it will extract the name, description and imageURL from the result and build an Animal object for each. You will see the “sqlite3_column_text” function used here, there are many more of these for returning other field types such as “sqlite3_column_int” for integers, “sqlite3_column_blob” for blobs or “sqlite3_column_value” to get an unknown value.

Now that we have the data in our array and we have it in our known format we are ready to start displaying it.

Open up the “RootViewController.m” file and edit the numberOfRowsInSection to look like the following:

SQLiteTutorialAppDelegate *appDelegate = (SQLiteTutorialAppDelegate *)[[UIApplication sharedApplication] delegate];
return appDelegate.animals.count;

What this does is it creates a link to the application delegate, and then the second line returns the size f the animals array in out Application delegate, this array was filled previously from the SQLite database.

Now in the cellForRowAtIndexPath function you will need at change it to look like the following:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}

// Set up the cell
SQLiteTutorialAppDelegate *appDelegate = (SQLiteTutorialAppDelegate *)[[UIApplication sharedApplication] delegate];
Animal *animal = (Animal *)[appDelegate.animals objectAtIndex:indexPath.row];

[cell setText:animal.name];
return cell;
}

We pretty much just added 3 lines under the “// Set up the cell” line, the first one is the same as we added previously to access the application delegate. The second line creates a new Animal object based on the array from the application delegate, it will be used to create a row for each individual record in the database. On the final line we are just setting the text of the cell to the name field from the Animal object.

You can now run the program and you should see a table view with the 4 animals we added to the database, if you added more than my default animals you should see them in here as well.

We will now setup the AnimalViewController, open up the “AnimalViewController.h” file and edit its contents to below:

#import 

@interface AnimalViewController : UIViewController {
IBOutlet UITextView *animalDesciption;
IBOutlet UIImageView *animalImage;
}

@property (nonatomic, retain) IBOutlet UITextView *animalDesciption;
@property (nonatomic, retain) IBOutlet UIImageView *animalImage;

@end

What we are doing above is adding an outlet for the description and image for the Animal, we will use these later on when we link the view up.

Now open up the “AnimalViewController.m” file and add a synthesize call for for the description and image, this will go under the “@implementation AnimalViewController” line, like so:

#import "AnimalViewController.h"

@implementation AnimalViewController

@synthesize animalDesciption, animalImage;

Now it is time to make the detailed view page appear when you select a record. Open up the “AnimalViewController.xib” file from the resources folder and the interface builder should appear.

The first thing we need to do is to set the File’s Owner Class to AnimalViewController, this is done by selecting the “File’s Owner” item in the main window and then clicking Tools > Identity Inspector in the top menu, and then selecting AnimalViewController from the class dropdown.

Your inspector window should now look like this:

We are going to be using a UITextView for the description (as it will allow for word wrapping and scrolling in the case that the description is quite large) and a UIImageView to display the image. I have laid mine out like below:

Now that we have everything laid out it is time to link them all up, start by holding control and click+drag from the “File’s Owner” to the “View” objects, a little gray menu will appear and you will need to select view. Now hold control and click+drag from the “File’s Owner” to the UITextView in the layout window, you should see “animalDescription” in the popup list, select it. Repeat this process for the UIImageView and you should see animalImage appear, select it also.

Now save the interface and close the interface builder.

Nearly done! All we have to do now is to setup the code for when a user presses on a record in the table view.

Open up the “RootViewController.h” file and edit its contents to below:

#import 
#import "AnimalViewController.h"

@interface RootViewController : UITableViewController {
AnimalViewController *animalView;
}

@property(nonatomic, retain) AnimalViewController *animalView;

@end

We are creating an instance of the AnimalViewController to be used bu the RootViewController when a user presses on an item.

Now open up the “RootViewController.m” file and edit the top part of the file to look like below:

#import "RootViewController.h"
#import "SQLiteTutorialAppDelegate.h"
#import "Animal.h"

@implementation RootViewController
@synthesize animalView;

This will just synthesize the animalView that we just added.

First up lets set the default title of our view, to do this you need to uncomment the viewDidLoad function, and edit it to below:

- (void)viewDidLoad {
[super viewDidLoad];
// Uncomment the following line to add the Edit button to the navigation bar.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;

self.title = @"My Zoo";
}

We also need to edit the didSelectRowAtIndexPath

function in this file, edit it to look like below:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic -- create and push a new view controller
SQLiteTutorialAppDelegate *appDelegate = (SQLiteTutorialAppDelegate *)[[UIApplication sharedApplication] delegate];
Animal *animal = (Animal *)[appDelegate.animals objectAtIndex:indexPath.row];

if(self.animalView == nil) {
AnimalViewController *viewController = [[AnimalViewController alloc] initWithNibName:@"AnimalViewController" bundle:nil];
self.animalView = viewController;
[viewController release];
}

// Setup the animation
[self.navigationController pushViewController:self.animalView animated:YES];
// Set the title of the view to the animal's name
self.animalView.title = [animal name];
// Set the description field to the animals description
[self.animalView.animalDesciption setText:[animal description]];
// Load the animals image into a NSData boject and then assign it to the UIImageView
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[animal imageURL]]];
UIImage *animalImage = [[UIImage alloc] initWithData:imageData cache:YES];
self.animalView.animalImage.image = animalImage;

}

What we are doing here is checking to see if the animalView object has already been created, if not then create it.

The next few lines are used to setup the animation (slide from right to left) and to set the actual data fields to those of the selected animal.

Now you should be ready to fire up the application and see it in all its glory.

You should see your windows looking like below.

5. Project Files

Here are the source files for this project: Download the project source files

If you have any questions or comments please feel free to post them here and I will try to get back to you ASAP. Also keep your eye out for my second part to the advanced RSS Reader tutorial, it will be using TouchXML, SQLite, UIWebview and more!

Thứ Năm, 5 tháng 8, 2010

How To Integrate iAd into Your iPhone App


1) Integrate iAd 2) ??? 3) PROFIT!

1) Integrate iAd 2) ??? 3) PROFIT!

With the iOs SDK 4 now public and the advent of iAds just a few days away, I thought we’d celebrate with a tutorial on how to integrate iAd into your iPhone app!

In this tutorial, not only will we show you how to get started with iAd, but we’ll also show you how to deal with some complex issues you may run into along the way such as:

  • Supporting both Portrait and Landscape ads in the same app
  • Integrating into a Universal app
  • Maintaining backwards compatibility with iOs 3.0
  • What to do if you are using a UITableViewController!

We’re actually going to start with where we left off in the How To Port an iPhone Application to the iPad and use the universal app we developed in that tutorial in the starting point.

So grab a copy if you haven’t already, and let’s get to adding some iAds!

Base SDK vs. Deployment Target

The first step to use iAd is to make sure our project has the right Base SDK and iPhone OS Deployment Target selected.

For those of you confused about the difference between the Base SDK and Deployment Target (like I was for quite some time!), here’s what they mean:

  • The Base SDK is the version of the SDK you are linking against. Your app can use any classes or functions available in the version of the SDK you choose here – as long as they are available on the actual device the code runs on.
  • The Deployment Target is the earliest possible version of the SDK your code can run on. This can be an earlier version than the Base SDK – in fact you often want to set it to be earlier to ensure that as many different versions of the OS can run your code as possible!

The tricky bit is what happens when you want to use a class, function, or framework available in one version of the OS if it’s available, but still work on the old version of the OS if it isn’t. We already did some of this in How To Port an iPhone Application to the iPad, and we’ll do even more in this tutorial!

For this tutorial, we want to set things up so that our code can use stuff available in iOS 4.0 (such as iAd), but still run on as many devices as reasonable (3.0+).

So first let’s set iOs 4.0 as the base SDK. To do this, expand the Targets directory, right click on PortMe, and choose “Get Info”. Click the Build tab, make sure “All Configurations” is selected, navigate to Architectures\Base SDK, and change the value to iPhone Device 4.0.

Screenshot of Setting Base SDK

Then, let’s set iPhone OS 3.0 as the iPhone OS Deployment Target. To do this, still in the Target Build tab, navigate to Deployment\iPhone OS Deployment Target, and change the value to iPhone OS 3.0.

Screenshot of setting Deployment Target

You should now be able to compile and run your app (use the iPhone simulator), and try it out on an iPhone 4 simulator. Once you run your code, in the simulator choose Hardware\Device\iPhone OS 4 and re-run your app. The simulator window will look a little different, and say iPhone 4 in the toolbar, so you’ll know it’s working!

Screenshot of PortMe on iOS4

Linking Against the iAd Framework

The next thing we need to do is add the iAd framework to the project. You can do this by right clicking on Frameworks, choosing “Add\Existing Frameworks…”, and choosing “iAd.framework”.

The problem is, if that is all we do our code will break on older devices that don’t have the iAd framework.

You can verify this by trying to run your code in the iPad Simulator 3.2 – boom! The app will crash on startup and you’ll see the following error log:

dyld: Library not loaded: /System/Library/Frameworks/iAd.framework/iAd
Referenced from: /Users/rwenderlich/Library/Application Support/
iPhone Simulator/3.2/Applications/
3ACB1BDA-26F6-43A6-84EA-9FB637B8CDCD/PortMe.app/PortMe
Reason: image not found

To fix this, we need to weak link against the iAd framework. Expand the Targets directory, right click on PortMe, and choose “Get Info”. Click the Build tab, make sure “All Configurations” is selected, and navigate to Linking\Other Linker Flags. Double click on that entry, click the “+” button, and type “-weak_framework iAd”.

Click OK, and then try your app on the iPad simulator again and viola – it should work!

Preparing our XIB

In this tutorial, we’re going to integrate iAd into both the PortMeGameListController and the PortMeGameDetailsController. However, the integration is a bit easier in the PortMeGameDetailsController because it is a subclass of UIViewController, so we’re going to start there first.

Open up PortMeGameDetailsController.xib. You’ll see that all of the controls are children of a single view:

Details View Controller Settings - Before

What we’re going to need to do with iAd is scroll an ad view onto the screen when an ad is available, and shrink the rest of the content to fill the remaining space. As currently designed, this isn’t that easy because all of the controls are direct children of the root view. But there’s an easy way to fix it – we’ll simply move the controls into a subview instead!

The easiest way to do this is to drag another view from the library into the XIB, and change its size to be the same as the existing view’s size (320×416). Then drag the existing view as a subview of the new view. When you’re done, it should look like the following:

Details View Controller Settings - After

Then, control-drag from the File’s Owner to the new view (which is now the root view) to connect it to the view outlet. Save your XIB, and run the project and verify that everything still works OK with the details view (in particularly that orientation resizing works correctly). If all works well, we’re one step closer to integrating iAd!

Simple iAd Integration

Ok, now let’s get to the fun part – integrating iAd!

First, make the following changes to PortMeGameDetailsController:

// In the import section
#import "iAd/ADBannerView.h"

// Modify the PortMeGameDetailsController interface
@interface PortMeGameDetailsController : UIViewController
{

// Inside the PortMeGameDetailsController interface
UIView *_contentView;
id _adBannerView;
BOOL _adBannerViewIsVisible;

// After the interface
@property (nonatomic, retain) IBOutlet UIView *contentView;
@property (nonatomic, retain) id adBannerView;
@property (nonatomic) BOOL adBannerViewIsVisible;

We first include the iAd headers and mark the view controller as implementing the ADBannerViewDelegate. This way, we can receive events as ads become available or not.

We then declare a property to keep track of the content view that contains all of the controls (basically the inner UIView). We also declare a variable to keep track of our iAd banner view, and whether or not it’s currently visible.

Note that we declare the iAd banner view as an id variable rather than as a ADBannerView. This is because we want to ensure backwards compatibility all the way to OS 3.0, and the ADBannerView class is only available on 4.0+, so we need to weak link against it.

Before we forget, let’s hook up our content view to the new outlet we just made. Make sure you save PortMeGameDetailsController.h, go back to PortMeGameDetailsController.xib, control-drag from the File’s Owner to the inner (second) UIView, and connect it to the contentView outlet.

Then switch over to PortMeGameDetailsController.m and make the following changes:

// In the synthesize section
@synthesize contentView = _contentView;
@synthesize adBannerView = _adBannerView;
@synthesize adBannerViewIsVisible = _adBannerViewIsVisible;

// In the dealloc section
self.contentView = nil;
self.adBannerView = nil;

Next, we’re going to add the meat of the code. But there’s a lot of it – so let’s break it down into 6 steps.

1) Add helper functions to get height of iAd banner

- (int)getBannerHeight:(UIDeviceOrientation)orientation {
if (UIInterfaceOrientationIsLandscape(orientation)) {
return 32;
} else {
return 50;
}
}

- (int)getBannerHeight {
return [self getBannerHeight:[UIDevice currentDevice].orientation];
}

There are several places in the rest of the code where we’re going to want to know how large the banner view should be given a particular orientation. Currently iAds have two possible sizes: 320×50 for landscape, or 480×32 for portrait. So we simply retrieve the proper height based on the passed in orientation.

2) Add helper function to create the iAd view

- (void)createAdBannerView {
Class classAdBannerView = NSClassFromString(@"ADBannerView");
if (classAdBannerView != nil) {
self.adBannerView = [[[classAdBannerView alloc]
initWithFrame:CGRectZero] autorelease];
[_adBannerView setRequiredContentSizeIdentifiers:[NSSet setWithObjects:
ADBannerContentSizeIdentifier320x50,
ADBannerContentSizeIdentifier480x32, nil]];
if (UIInterfaceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
[_adBannerView setCurrentContentSizeIdentifier:
ADBannerContentSizeIdentifier480x32];
} else {
[_adBannerView setCurrentContentSizeIdentifier:
ADBannerContentSizeIdentifier320x50];
}
[_adBannerView setFrame:CGRectOffset([_adBannerView frame], 0,
-[self getBannerHeight])];
[_adBannerView setDelegate:self];

[self.view addSubview:_adBannerView];
}
}

This helper function creates an ADBannerView in a manner that is safe to use across multiple OS versions. It uses weak linking and NSClassFromString to check if the ADBannerView class is available – if it is not, the method will return nil and the function will bail.

However, if it is available it creates an instance of the class. It then uses the setRequiredContentSizeIdentifiers to specify what kind of ads this app needs. For our case, our app supports both portrait and landscape modes so it needs both ad options.

It then calls setCurrentContentSizeIdentifier to tell iAd which ad it should display. We simply choose the correct one by looking at the current orientation.

Next, we need to set the frame for the iAd. Note there’s some funky business here – we actually set the frame of the view to be offscreen! This is because we don’t know if an ad is available yet, and we don’t want to display the view until we know one is.

We set our view controller as the delegate so that we can receive notice about iAds being available or not. Then finally we ad the new iAd banner view as a subview of our view!

Note something subtle about the above – we always use message passing syntax rather than dot notation (i.e. [_adBannerView setRequiredContentSizeIdentifiers:...] instead of _adBannerView.requiredContentSizeIdentifiers = …). This is again to make sure everything runs fine on OS 3.0+.

3) Add function to size views correctly

- (void)fixupAdView:(UIInterfaceOrientation)toInterfaceOrientation {
if (_adBannerView != nil) {
if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) {
[_adBannerView setCurrentContentSizeIdentifier:
ADBannerContentSizeIdentifier480x32];
} else {
[_adBannerView setCurrentContentSizeIdentifier:
ADBannerContentSizeIdentifier320x50];
}
[UIView beginAnimations:@"fixupViews" context:nil];
if (_adBannerViewIsVisible) {
CGRect adBannerViewFrame = [_adBannerView frame];
adBannerViewFrame.origin.x = 0;
adBannerViewFrame.origin.y = 0;
[_adBannerView setFrame:adBannerViewFrame];
CGRect contentViewFrame = _contentView.frame;
contentViewFrame.origin.y =
[self getBannerHeight:toInterfaceOrientation];
contentViewFrame.size.height = self.view.frame.size.height -
[self getBannerHeight:toInterfaceOrientation];
_contentView.frame = contentViewFrame;
} else {
CGRect adBannerViewFrame = [_adBannerView frame];
adBannerViewFrame.origin.x = 0;
adBannerViewFrame.origin.y =
-[self getBannerHeight:toInterfaceOrientation];
[_adBannerView setFrame:adBannerViewFrame];
CGRect contentViewFrame = _contentView.frame;
contentViewFrame.origin.y = 0;
contentViewFrame.size.height = self.view.frame.size.height;
_contentView.frame = contentViewFrame;
}
[UIView commitAnimations];
}
}

This is a helper function we can call to make sure our views are in the right position. If ads are available, we want the ad banner view to be at the top of the screen and the content view shrunk a bit to fill the rest of the area. If ads are not available, we want the ad banner view offscreen and the content view as large as the entire view here.

And that’s exactly what the above function does. It looks long, but is fairly simple and self-explanatory. Note that we wrap the resizing code in an animation block to make things look awesome.

4) Call createAdView in viewDidLoad

- (void)viewDidLoad {
[self createAdBannerView];
}

We want to create our ad view as soon as our view is loaded, even if we aren’t ready to display it quite yet.

5) Call fixupAdView in viewWillAppear and willRotateToInterfaceOrientation

- (void) viewWillAppear:(BOOL)animated {
[self refresh];
[self fixupAdView:[UIDevice currentDevice].orientation];
}

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[self fixupAdView:toInterfaceOrientation];
}

We need to fix up our ad view in viewWillAppear, because the device may have changed orientations in the time between when our view was visible last and now. And we obviously need to change it upon rotation as well!

6) Implement ADBannerViewDelegate

#pragma mark ADBannerViewDelegate

- (void)bannerViewDidLoadAd:(ADBannerView *)banner {
if (!_adBannerViewIsVisible) {
_adBannerViewIsVisible = YES;
[self fixupAdView:[UIDevice currentDevice].orientation];
}
}

- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
if (_adBannerViewIsVisible)
{
_adBannerViewIsVisible = NO;
[self fixupAdView:[UIDevice currentDevice].orientation];
}
}

Now that we have our helper functions, implementing the ADBannerViewDelegate methods are quite simple. We simply toggle whether the ad banner view should be visible or not, and call fixupAdView.

Done!

And that’s it! Compile and run your project, and you should see ads appear correctly in both portrait and landscape mode.

iAd in List View - Portrait Mode

iAd in List View - Landscape Mode

And best yet – if you run the code on an iPad or iPhone 3.0 device it will work just fine as well, but without ads!

UITableView integration

Well that worked great for our details controller, but we want it in our list controller too!

The problem is our list controller is a UITableViewController. Unfortunately, it seems like the best way to deal with this situation is to convert your UITableViewController to a normal UIViewController and then proceed similarly to the way we did above. So here are all of the gory steps:

1) Create a XIB for PortMeGameListController

Go to File\New File, choose User Interface and View XIB, make sure Product is iPhone, and click Next. Name the XIB PortMeGameListController.xib and click Finish.

Open up the XIB, click on the File’s Owner, and in the fourth tab of the Attributes Inspector change the class to PortMeGameListController.

Then drag a UIView into the current UIView (so there are 2, just like we did before), and add a UITableView to the inner view. When you’re done it should look like this:

XIB settings for PortMe's List View Controller

2) Make some changes to PortMeGameListController

Inside PortMeGameListController.h:

// Change the interface declaration
@interface PortMeGameListController : UIViewController {

// Add inside class
UITableView *_tableView;
UIView *_contentView;

// Add after class
@property (nonatomic, retain) IBOutlet UITableView *tableView;
@property (nonatomic, retain) IBOutlet UIView *contentView;

Inside PortMeGameListController.m:

// In synthesize section
@synthesize tableView = _tableView;
@synthesize contentView = _contentView;

// In dealloc section
self.tableView = nil;
self.contentView = nil;

Don’t forget to save the files!

3) Hook up outlets

Now go back to PortMeGameListController.xib and connect the first view to the view outlet, the second to the contentView outlet, and the third to the tableView outlet.

Also control-drag from the tableView back to the File’s Owner and set it as the delegate and datasource.

4) Set the NIB name for PortMeGameListController in MainWindow

Open MainWindow.xib and MainWindow-iPad.xib, expand the Navigation Controller, select “Port Me Game List Controller”, and change the nib name to PortMeGameListController.

5) Compile and test to make sure everything works as usual

At this point, compile and run your code and make sure everything works as it usually does – but now you’re using a UIViewController rather than a TableViewController, and you have a XIB laid out in a nice way to use iAds!

6) Follow the steps from the previous section

Now you’re exactly where we were in the previous section with a view controller – so follow the same steps to integrate in this view!

Done!

If all goes well, you should be able to compile and run your project and see advertisements at the top of your table view!

iAd in Table View - Portrait Mode

iAd in Table View - Landscape Mode

Where To Go Now?

Here is a sample project with all of the code we’ve developed in the above tutorial.

Now you should know how to integrate iAds into your projects – no matter what OSs you wish to support for your app! I’d love to hear your experiences with iAds and how well they are working (or not) for your app!

Category: iPhone

Tags: , ,





88 Comments

  1. Marin Todorov (9 comments) says:

    Ray you are a pure inspiration :) If I could only quit my day job I’d be following in your footsteps to share iPhone knowledge to everyone in such a cool way

    Best, wishes

  2. Shawn Grimes (1 comments) says:

    Awesome tutorial Ray. Thanks so much for the info, I’m looking forward to giving this a try. I really like that you took iOS 3.0 into consideration with this and provided code that also provided backwards compatibility components.

  3. Abhinav (2 comments) says:

    Hey.. Very useful.. I was just wondering how to start and u finished it up… Thank You.

    When i run it on my iPad, I cant see the advertisement. Why is that so?

  4. Ray Wenderlich (399 comments) says:

    @Marin, @Shawn: Thanks guys for the kind words! :]

    @Abhinav: iAds are only available on iOS4+, so they will not work on iPad yet!

  5. Abhinav (2 comments) says:

    Ok. So, When it will be available on iPad..??
    Can I upgrade iPad to 4.0?

  6. olinsha (11 comments) says:

    Hi Ray,
    I have finally decided to add a comment to one of your tutos just to let you know that your site has become my number one information source for iPhone dev.
    Your tutos are clear and very helpfull.
    So keep going and share your great knowledge with us to build a big community that will make the development skillz for iPhone / iPad evolving !

    best regards from France ;)

  7. Ray Wenderlich (399 comments) says:

    @Abhinav: Not sure when iOS4.0 will be available for iPad, but for now – unfortunately no iAds on iPad!

    @olinsha: Wow, thanks for the kind words! I think we have an awesome and helpful community of iPhone developers so trying to do my part!

  8. Alon Ezer (2 comments) says:

    Hey Ray,
    Thanks for the tutorial.

    Could you explain how to implement it in cocos2d?

    Thanks!!

  9. Vlad Alexa (1 comments) says:

    Declaring the ADBannerView as id is totally unnecessary since you already weak linked the framework, which can be done more easily by changing the type to weak in the general tab of your target’s settings

  10. Asad (6 comments) says:

    Hello Ray

    i want to add a button and when a user tape the button , the player should move, but i want to add 4 sided button like when the user drag the button on right side the player should move on the right side and when the user drag the button on left side the player should move onto the left side.i want to use one button instead of 4 buttons and want 4 functionality from the one button rather than using 4 buttons because iphone screen is limited.
    please reply me, i need your help.

    thanks Ray

  11. Ray Wenderlich (399 comments) says:

    @Alon Ezer: Maybe in a future tutorial, we’ll see! But to get you started, you should be able to just put the Cocos2D view inside a view controller, and then follow the rest of the tutorial as-is.

    @Vlad: Good points on both cases, didn’t realize that, thanks for the tips!

    @Asad: I think you are posting in the wrong thread, this is a thread about integrating iAd ;]

    However, it sounds like you are looking for a joystick implementation in Cocos2D. There are a bunch of guys that have released code for this, here’s a link to one that looks good:

    http://github.com/sneakyness/SneakyInput

  12. Thang Tran (15 comments) says:

    Hi Ray,

    thank you very much for this helpful tutorial. That’s exactly what I’m looking for to solve my problem.

    I did it so well with your tutorial’s supports.

    However, I have one question. I’ve got so many “warning”… it’s about “invalid receiver type ‘id*’”

    Of course the application is running well. However, I don’t know if those warning make my app got rejected on the AppStore. Do you have any ideas?

    Best Regards,
    Thang

  13. Ray Wenderlich (399 comments) says:

    @Thang: Hm, not sure I just re-built the sample project and don’t get any warnings. Do you see warnings compiling the sample project?

    One thing to check is that you declared your id variables like “id foo”, not “id * foo”.

  14. Thang Tran (15 comments) says:

    Hi Ray,

    thank you very much. I’ve solved the problem. I didn’t notice that I should declare the id adView instead of id *adView.

    However, I’ve been getting some warnings even in the auto-gererated code from XCode. Like when I use UITableView, I got a warning inside
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    I’m gonna submit my first app on AppStore. I’m wondering if the warning fail my app submission or not. The best way should be trying ;) I will try and let you know if I got a reject because of the warning.

  15. Dion (1 comments) says:

    thanks for the tutorial. i just have one question. is that it? once a app is submitted, if you the ads will simply appear instead of that Test advertisement message??

  16. Thang Tran (15 comments) says:

    @Dion: YES

    But in order to have it, you must complete a special contract on iTunes Connect (like you did for selling apps) to have the ability to get the ads’ contents.

  17. Ray Wenderlich (399 comments) says:

    @Thang: Apple won’t reject you if your code has warnings (I doubt they will even know such things), however you should be careful about warnings because quite often they indicate bugs in your code that you should address. A lot of people like compiling their code with an option that sets warnings to errors just so they are forced to go through and clean up warnings.

    Thanks for answering Dion’s question too btw!

  18. Johng1492 (1 comments) says:

    Ray,

    Nice article but…

    I downloaded your code and ran it without change and the iAd test ads do not come up. Is there something on my end that I need to set in order to get your app functioning?

    Good work.

    John

  19. Marin Todorov (9 comments) says:

    Hi Ray,

    I know I already left a comment, but I’m banging my head against the wall here, so I thought you might give me a tip …

    When I installed the latest XCode I went straight to try your iAd example – and it works great in the iPhone 4 Simulator and the iPad Simulator, but I want to actually be sure it works on 3.1 and I see the 3.1 images are there in the Simulators folder, but XCode does not list them anymore … What do you say, did you find way to test on 3.1 Simulator somehow ?

    Thanks a lot, hope you have the right tip as always,
    Marin

  20. Thang Tran (15 comments) says:

    @Ray: thanks for your comment
    @Marin: I’m afraid that you can’t test on the 3.1 simulator. But you can test on the 3.1+ devices. My iPhone is running 3.1.3 and I can test the app when I plug my iPhone with XCode, and I selected Device 4.0

  21. Ray Wenderlich (399 comments) says:

    @John: Just downloaded the sample code and tried it out, and I see ads on my end. So not sure! Could be maybe the test ad server was down, or some kind of internet connectivity issue?

    @Thang: Thanks again for helping out! Yeah that’s what I did back when my device was running 3.1.3 too… however now that it’s running 4.0 not sure what I’ll do for testing here on out!

  22. Thang Tran (15 comments) says:

    @Ray: it has been being a big discussion on devforum.apple.com

  23. olinsha (11 comments) says:

    Hello Ray,
    thanks to your tuto I switched one of my app from AdMob (totally screwed with iOS4) to iAd. On my iPhone using iOS 4 and using the latest X-Code it’s working fine
    But I also kept an older X-Code version to test my apps on older OS. And when I build my app for OS 3.1.3 I have plenty of errors (42 !!) that are all related to iAd :
    * iAd/ADBannerView.h: No such file or directory
    * cannot find protocol declaration for ‘ADBannerViewDelegate’
    and so on.
    I don’t understand the issue as I made iAd framework dependance weak. So I am not sure that we can really make such application run on older OS.

  24. Asad (6 comments) says:

    hi Ray

    i have integrated iAd in my app with the help of your great tutorial but i don’t know whether it shows only one ad or there is a log in account or a secret key (like in case of admob, mobclix) to show ads changing after some instant of time.

    Thanks in advance….

  25. Eric (15 comments) says:

    This is really nice. We were trying to get it to do the same thing, but have the ad on the bottom, and the image move up. I see how to move where the ad is, but the image is moving always up. I am trying to adjust some of the logic but so far no luck.

    Any suggestions? Thanks!

  26. Ray Wenderlich (399 comments) says:

    @olinsha: An app with iAd will not compile with an old version of the SDK because it requires symbols in the iOS4+ SDK. However, the app itself should run just fine on devices running iOS 3.0+ if you build it with the above instructions. The best way to test is on a device running iOS3.0 AFAIK.

    @Asad: You don’t need to integrate a secret key or anything like some other APIs.

    @Eric: What do you mean the image is always moving up? I assume you already modified the code in fixupAdView to put the ad on the bottom and the content view a bit smaller but on top?

  27. John (3 comments) says:

    Ray,

    Have you found problems when you have a view that is nested below Nav controllers, tab views and in a table?

    I get your app working (and the WWDC app sample) working no problem. But when I try to include it in my working app with tab views/nav controllers and a table view, the apps don’t want to show.

    Thoughts?

    Great work.

    John

  28. Marin Todorov (9 comments) says:

    Other people tell that installing the old XCode on an external drive will let you use the 3.1.3 Simulator, didn’t try it myself, but probably in the weekend I’ll have time to give it a try!

  29. Bob Schoenburg (6 comments) says:

    I “grafted” the code into one of my existing apps, and it works! One problem: sometimes I get compiler complaints about mycontroller may not respond to fixupAdView????

    Thanks for a great tutorial

  30. samson (11 comments) says:

    First, thanks a lot for the great article.

    Question: I followed the article and can see the “test ad” in simulator. But when I load the app onto my test device (iPhone 3GS with OS 4.0), the ad bar is always blank, doesn’t show the test message. And console shows this:

    Error Domain=ADErrorDomain Code=0 “Invalid app id/account information” UserInfo=0×26eac0 {NSLocalizedDescription=Invalid app id/account information}

    Did I do anything wrong or is it expected behavior? (e.g., ad won’t show up on device until approval?)

    I already submitted the app for review anyways. During uploading, I made sure I did turn on iAd, and completed the contract process.

    Thanks!

  31. Ray Wenderlich (399 comments) says:

    @John: I’ve tried it in several of my apps that have nav controllers without any issues – haven’t tried it with tab bar controllers but I can’t see why that shouldn’t work. Have you tried debugging through and seeing if the resize code gets called and printing out the frames of the views, etc?

    @Marin: You’re right about that, in fact Matt Gallagher just came out with a nice post that mentions this as well as some other useful tips:

    http://cocoawithlove.com/2010/07/tips-tricks-for-conditional-ios3-ios32.html

    @Bob: To avoid those warnings, make sure fixupAdView is declared before you call it, or add the signature of fixupAdView to your class’s header.

    @samson: Hm, I haven’t seen that problem. Maybe it was due to timing – when you create the app in iTunes Connect, you have a checkbox to enable the app for iAd support. Let me know if it’s working for you when it gets approved.

  32. Bob Schoenburg (6 comments) says:

    Hi Ray,

    The problem indeed got fixed when I declared the items in the .h files as needed.

    Thanks

  33. Samson (11 comments) says:

    Ray, thanks for the reply.

    I double checked my app on ITC. It is waiting for review and the info does say “iAd is enabled”. So I guess it should be fine. I will keep you updated once it’s approved (or rejected for reasons related to iAd).

    Btw, I forgot to mention in my previous message: that error message was actually printed from adbannerviewdelegate’s didFailToReceiveAd callback.

  34. ltgbau (2 comments) says:

    Hi Ray!
    Thanks for the tut, I’ve just intergrated iAds to my app. I’ll test and submit it soon.
    Thanks again!

  35. Tony (2 comments) says:

    downloaded your code, ran in and rotate (iad) work perfectly , while in landscape i click in test advert and get this in debugger

    The view controller returned NO from -shouldAutorotateToInterfaceOrientation: for all interface orientations. It should support at least one orientation.

    Normal ?

    Great Tutorial :)

    T

  36. Joseph Nardone (3 comments) says:

    Great article.

    I am having the same problem as Sampson. I have successfully integrated iAds into several new apps but when I activated iAds for an old app and then added my functional iAd code to the debug version, I get the same error message as Sampson on the device.

    I can’t run on 4.0 simulator because I have Quattro SDK installed for back-up ads.

    Thanks,
    Joseph

  37. Forrest (5 comments) says:

    Anyone had tried to integrate iAds into three20 ? To be more specific, how about TTPhotoView ?

  38. Ray Wenderlich (399 comments) says:

    @Samson/@Joseph: Yeah I get that shouldAutorotateToInterfaceOrientation error too. Maybe a bug with the test ads…? Either way, doesn’t seem to cause a problem in my shipped apps with iAds.

    @Forrest: I have not, but I’d imagine it would involve a lot of digging around Three20 code unfortunately :P Maybe try the Three20 forums on this one, they might have some good tips to get you started on that.

  39. Joseph Nardone (3 comments) says:

    Thanks Ray but that is not the error we were talking about. We got an “Invalid app id/account information” when running on the device.

    Mine went away and turned into a “no qualified ads” error so I still get no test ads with working code on my device.

    Joseph

  40. @gojia (1 comments) says:

    hi, ray.
    I’m always thanks for your good tutorial.

    I’m trying this IAD tutorial.
    but I have some problem.

    4.0, 3.1.3 Simulator no problem!
    4.0 Device no problem.
    but 3.1.3 Device have error. like this.

    warning: Unable to read symbols from “iAd” (not yet mapped into memory).
    Error launching remote program: security policy error
    The program being debugged is not being run.

    What should I have to??
    Thank you for your time.

  41. samson (11 comments) says:

    @ray, the issue Joseph and I met should have nothing to do with auto-rotation. It’s that the ad is always blank and the delegate’s didFailed… is called with “invalid app id/account” error. Also the following seems to be true for both of us:

    - Only occurs on device. On simulator it does show “test ad”. (Seems Joseph can’t run simulator for some reason but he does see the same problem on device)

    - Occurs only when you try to upgrade an old app with iAd. (I haven’t tried any new apps but Joseph said new apps are fine)

    @tony: the auto rotation error you saw is more like a warning. To avoid it you just need to do this:

    - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    return interfaceOrientation == UIInterfaceOrientationPortrait;
    }

    This tells the OS your app only supports portrait mode and can’t rotate at all.

    Somehow the template code doesn’t write this part for you. Apparently it still works the same, but the OS is just reminding you to be more specific.

  42. Asad (6 comments) says:

    Hello RaY

    I have implemented iAd in my application but it only shows Test Advertisement on ads. i have agreed to iAd agreement also. My question is that whether this ad is changing or its statically only one ad, i also want to upload my app to appstore but it contains only one ad. i think if my app gets ready for sale, it will change ads automatically.
    please give me some help in this regards.

    Thanks

  43. Thang Tran (15 comments) says:

    @Asad: you Ad will be fully controlled by Apple iAd server. So you don’t need to care about changing you ad. The only task you have to do after signing the iAd contract is integrate ADBannerView into your apps.

    The actual ads will appear when people download your app from AppStore.

  44. Asad (6 comments) says:

    Special thanks to Thang Tran, i am going to upload my app to appstore.

  45. Thang Tran (15 comments) says:

    @Asad: you are welcome :)

    Congratulation, you are going to have a new app your the store, and going to make money. Just share the link when it is available on the store. Best wishes.

  46. Bob Schoenburg (6 comments) says:

    Uploaded my app, waiting for review. Thanks to all for the help and comments.

  47. samson (11 comments) says:

    Hi All,

    I just found out something: after you login ITC, there is an item called “iAd networks”. From here you can check your app’s iad state. For example mine shows:

    Testing: This app is receiving test ads. This app’s status is not yet Ready For Sale. You have not configured ad preferences for this app.

    Well this still doesn’t explain why it shows blank id and invalid id error on device, but hope it’s helpful for others.

  48. MarkAtAR (3 comments) says:

    I seem to be getting EXC_BAD_ACCESS crashes in the simulator after adapting the code (without rotating) into my app. Has anyone else experienced this, or does anyone know where I could have gone wrong?

    I have the correct code in the dealloc section, that was my first double check.

  49. Ray Wenderlich (399 comments) says:

    @Josesph/@samson: OK, I did some more testing on this… I ran both one of my apps with shipped ads, and the sample code above on my device, and here are the results:

    * My app with shipped ads: shows actual ads – I guess because the app has been enabled for iAds & approved…?
    * The sample project above: shows test ads.

    If you guys run the sample project above on your device, does it show test ads or do you get the error?

    Did you guys go to iTunes connect and enable your iAd network contract?

    @gojia: Unfortunately I no longer have a 3.1.3 device to test that out on… if anyone else does and can help @gojia out (and let me know if I’ve made some kind of mistake in the project above), I’d greatly appreciate it!

    @Thang: Thanks for helping out @Asad!

    @MarkAtAR: When you get an EXC_BAD_ACCESS, there are two ways I usually try to track that down: 1) set breakpoints in the code to narrow down the area nearby where it occurs, and 2) enable NSZombieEnabled for the project, which sometimes help give a hint about why it’s occurring:

    http://www.cocoadev.com/index.pl?NSZombieEnabled

  50. Joseph Nardone (3 comments) says:

    Hi Ray:
    Thanks, I contacted Apple and they said to file a support ticket as something is not set right in the iTunes Connect portal for that one particular app. It probably has to do with the fact that it was an update to an app that was activated for iAds before iAds launched and then later rejected. All my newer apps work fine.

    I have another question for you about the iAds in your live app. When you look at the console for the live app can you tell how long after a “ad not received error” the banner attempts to reload. Is it 30 seconds like in the test ads or 3 minutes – the reported refresh rate for successful loads?

    Thanks again,

    Joseph Nardone

  51. MarkAtAR (3 comments) says:

    @Ray

    Thanks for the quick reply Ray. I have ads on multiple view controllers and the error I am getting is this:

    *** -[MenuViewController bannerViewDidLoadAd:]: message sent to deallocated instance 0×141430

    But MenuViewController is the first view controller and this error only happens if you go to the next view controller before the first ad is able to load. Then it gives me that error above with NSZombiesEnabled. I think I am misunderstanding what is being done by the ads in the background or I am doing something seriously wrong. Does this make any sense?

  52. Chris Dunphy (2 comments) says:

    Thank you for the great tutorial – it has been a life saver.

    I only have one (really annoying) problem left. When I changed my UITableViewController into a UIViewController, the TableView now longer seems to respond to my ToolBar hiding / unhiding.

    When the ToolBar is displayed, the last row of my table ends up hidden behind it.

    I am beating my head against the wall figuring out how to get the auto-sizing to work for the ToolBar.

    Everything else is working great, other than the last line of my table getting lost when the toolbar displays.

    Thoughts?

  53. samson (11 comments) says:

    @MarkAtAR: bad_access usually occurs when you use a pointer whose memory is already freed.

    Did you set your adBannerView.delegate = NULL in your viewcontroller’s dealloc? This, among many others, is a very common mistake.

    @Ray/Joseph,

    Looks like new apps are fine. Only upgraded ones have this issue.

    I also contacted ITC and haven’t received any feedback yet. But the feedback Joseph received makes sense.

    I’ll wait and see. Thanks.

  54. MarkAtAR (3 comments) says:

    @samson

    Yup, I have self.adBannerView = nil in my dealloc. And dealloc is definitely running when I leave the view, but the error still occurs.

  55. samson (11 comments) says:

    @MarkAtAR, yes you set the view to nil. But did you set the delegate to nil too? e.g., self.adBannerView.delegate = nil;

    According to documents, it’s very important to set the delegate to nil in dealloc because it is possible that the viewcontroller is dealloc’ed but adview is being held by something else and its reference count is still above 0. In that case, when something happens in adview and it needs to call the delegate, it doesn’t know the delegate is freed, hence the crash.

  56. macjasp (1 comments) says:

    Fantastic. One of the most concise and well written articles i’ve ever read. Explained it perfectly and I now have iAds intergrated into my already existing App.

    It’s now going to be interesting to see what I make with iAds rather than selling my App at a price – but that’s a different topic

  57. Thang Tran (15 comments) says:

    @macjasp: yeah, now we have more reasons to provide free apps on the App Store. We can earn money while we do branding on the App Store.

  58. Ray Wenderlich (399 comments) says:

    @Joseph: Ah good to know re: the support ticket – that explains why I didn’t see any problems I guess!

    Just tested out the refresh rate on my app with ads. Ran it in debug mode on my local device, and after getting a didFailToReceiveAdWithError callback, it appeared to keep trying every 30 seconds thereafter.

    @samson: Thanks for helping out @Mark! @Mark, did that resolve it for you?

    @Chris: How have you added the toolbar, is it part of the view controller and added/removed to the view dynamically? If you have the table view and toolbar in the same view controller, can’t you just set the frame of the table view to the right size when the toolbar displays?

    Just checked out your blog by the way – looks pretty interesting and a cool lifestyle you lead, I’ll be following your site from now on! :]

    @Thang/@mac: Personally I’ve had better luck with regular app sales than with iAds… however iAds are a helpful addition to the free apps I make with the goal of enticing people to upgrade for the full versions. However, it really depends on your app – and how many people you can get to download it! :]

  59. Thang Tran (15 comments) says:

    @Ray: yeah, you are right! This is the key point no matter if your app is free for paid :)

  60. samson (11 comments) says:

    Update on my app:

    1. It gets rejected because my app doesn’t hide the ad bar when the app fails to receive an ad. Looks like “hide when no ad” becomes a mandatory requirements. (Mine is actually a little special — I put ad in UITableView.tableHeaderView so it can be scrolled away. I don’t want to hide the ad because it will shift the rows and might cause user to tap a wrong row if ad update happens about the same time of tapping. Anyways I’m trying other method to comply.)

    2. From yesterday, my app on device no longer receives “invalid app id/account” error. Now it still never gets an ad, and didFailToReceiveAd… is still called all the time. Only difference is that the error msg now becomes:

    Error Domain=ADErrorDomain Code=0 “no qualified ads found for this request” UserInfo=0×13871140 {NSLocalizedDescription=no qualified ads found for this request}

    I guess Apple support team might have done something to my account and this error looks more normal… But it still doesn’t see the test ad. I’m waiting for their further response.

  61. Forrest (5 comments) says:

    @samson in order to hide ads if errors happen, maybe like this

    - (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
    {
    NSLog(@”ad is wrong “);
    if( error != nil ){
    if( bannerView != nil ){
    bannerView.hidden = YES;
    }
    }
    }

    @all
    Actually regarding the delegate, so odd that they seems like do not run , for example, I put NSLog(..) to print info inside those three delegates,

    - (BOOL)bannerViewActionShouldBegin:(ADBannerView *)banner willLeaveApplication:(BOOL)willLeave
    {
    NSLog(@”Banner view is beginning an ad action”)
    return TRUE;
    }

    - (void)bannerViewDidLoadAd:(ADBannerView *)banner {
    NSLog(@”ad is okay “);
    if( bannerView != nil ){
    bannerView.hidden = NO;
    }
    }

    - (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
    {
    NSLog(@”ad is wrong “);
    if( error != nil ){
    if( bannerView != nil ){
    bannerView.hidden = YES;
    }
    }
    }

    Tested on simulator, there was no any info printed out, just something like these , is that wrong ?

    ADManager: did enter background

    The view controller returned NO from -shouldAutorotateToInterfaceOrientation: for all interface orientations. It should support at least one orientation.

  62. Samson (11 comments) says:

    @Forrest, thanks for the info. I know how to hide the ad upon failure, just not sure if I want to do it — I put ad in tableheaderview of uitableview, if I hide/show ad, user will see the table shift up or down 50 px. If user is about to tap on a row, he/she might tap on the wrong one because of the shift. Maybe I should rethink the overall design — but I really like the way the user can scroll the ad off screen.

    For your other question — did you check all the simple things first, such as:

    - you did set delegate to self
    - you did set ad content size set, and current size
    - you did call request ad on adbannerview

    I know these are just wild guesses… But it doesn’t hurt to check simple things first.

    (btw the auto rotate error is irrelevant to iAd and is not important. See my earlier comment)

  63. Ray Wenderlich (399 comments) says:

    @samson: Good to know that hide when no ad is mandatory! I’m glad that first error went away, keep us posted and let us know if/when it starts showing up actual ads.

    Regarding your ad placement, why not implement it the same way as in this tutorial where there’s a banner view that appears above the table view? Personally I like having it permanently showing even if the user scrolls, because after all you don’t get any $$ if the user can’t see it! :]

    @Forrest: That’s not the best way to hide the banner view when the ad isn’t showing, it’s best to remove the view or move it offscreen because otherwise you just have a blank spot where the ad is supposed to be. I mentioned one way to do so in the above tutorial.

    Samson has some good ideas about how to narrow down where your problem is. If none of those work, try the code from the sample project which will give you a working base to start with.

  64. Ricky (5 comments) says:

    Hi Ray, thanks so much for the tutorial, really helpful!
    I’ve only a question : i’m creating an app that has a lot of table views, navigation controller-based. Have I to put an iAd on any table view or only on the root view? What does Apple reccomends? Thanks very much :)

  65. Ray Wenderlich (399 comments) says:

    @Ricky: I haven’t seen an Apple doc with any specific recommendations of whether to put it just on the root view or all views… but personally, my rule of thumb is if there’s a good chance the user will be looking at a view for a period of time, in my apps I’ll put an ad view there, even if on multiple drill-down views. After all, more ads = more $$!

  66. Bob Schoenburg (6 comments) says:

    Hi Ray,

    Thanks to you, my app is now up.

    http://itunes.apple.com/us/app/icoin-free/id381816540?mt=8

  67. samson (11 comments) says:

    @ricky, I have similar app structure — many levels hierarchy of UITableView. I put Ad in every of them.

    Just make sure you implement viewDidLoad/viewDidUnload pair properly so that your ad views in invisible views are freed when memory is low, and recreated when that view goes visible again. Otherwise you might run out of memory sooner than you should.

    Usually you just need to set all subview properties to nil — assume you declare them as “retain” property and recreate them in viewDidLoad.

    @ray, yes i probably should just make it simpler and put the ad view in a constant area. The reason of my current design was that I thought the screen was small enough…

  68. Ricky (5 comments) says:

    First of all, thanks so much for your answers.
    @Ray i haven’t seen it too and it was for this reason that i asked for. So, i’ll put adbanners wherever i’ll suppose they would be helpful. Thanks :)

    @samson: Yes, I set them to nil in the dealloc section and I create them in viewDidLoad. If banner is available I show it, else the frame of the adbanner view in out of the view and it’s set to hidden. In the tableviews, i don’t have the problem of resize the view if the banner is hidden because it stays in a separate view and i can scroll the table view up/down separately. But the $$ comes only if the user tap on the banner view?
    Really thanks :)

  69. Ricky (5 comments) says:

    Sorry for the double post… but so, in any view i want the iAd i have to create a view implement the banner and all its methods?

  70. samson (11 comments) says:

    @ricky, yes you need to implement the ad banner delegate for every view controller. All my views inherit from a super class I created, so I just do it once in that super class. Maybe creating a category in UIViewController is an option too.

    Regarding setting view to nil, doing it in dealloc is necessary but might not be sufficient. You should do it in viewDidUnload too. The logic here is that, when a viewcontroller becomes invisible (e.g., another view pushed into the top of navigation controller), the system might choose to free its views in case of low memory (but will not release the view controller object itself). When the system does so, it will call viewDidUnload. You should then release anything you explicitly created in viewDidLoad or held in a retained property. The next time this view controller goes visible again (e.g., the view that covers it gets popped out), viewDidLoad will be called again.

    I think most apps with iAd will create iAd view manually in viewDidLoad, so you should do the opposite in viewDidUnload. Otherwise the system’s memory management won’t work efficiently (worse, it could cause memory leak if you don’t realize viewDidLoad could be called multiple times in a viewcontroller’s lifecycle).

    You can test this behavior by adding log statement in viewDidLoad/viewDidUnload, and use simulator’s “simulate low memory warning” menu. You’ll notice that viewDidUnload of the invisible views are called when you choose this menu item, and viewDidLoad called upon when it goes visible again.

    For more details please refer to “memory management” paragraph of http://developer.apple.com/iphone/library/documentation/uikit/reference/UIViewController_Class/Reference/Reference.html

  71. Ricky (5 comments) says:

    Thanks samson, you are really really helpful!! :)

  72. Ray Wenderlich (399 comments) says:

    @Bob: Awesome, congrats!

    @Ricky: Yeah, similar to what Samson mentioned, for my apps I pulled the above code into a helper object to avoid all the code duplication.

    @Samson: Agreed, thanks for helping out Ricky here! Good point on the viewDidLoad/viewDidUnload scenario, that is something that took me a long time to figure out with iPhone dev :]

  73. Chris Dunphy (2 comments) says:

    Hi Ray –

    The quality of conversation and help in this thread is phenomenal – I love it.

    You asked: “How have you added the toolbar, is it part of the view controller and added/removed to the view dynamically?”

    I have a MainWindow.xib that has a Navigation Controller, featuring a Navigation Bar, a Toolbar, and a Root View Controller.

    The RootViewController.xib is now set up with a Table View contained within a View, contained within a view – as instructed in the tutorial here.

    “If you have the table view and toolbar in the same view controller, can’t you just set the frame of the table view to the right size when the toolbar displays?”

    I’m probably doing something simple wrong. The Table View used to resize automatically before I changed things to incorporate the iAd. Now the bottom row ends up obscured when the Toolbar slides up.

    “Just checked out your blog by the way – looks pretty interesting and a cool lifestyle you lead, I’ll be following your site from now on! :]”

    *blush* – Thanks! Nomadic life is fun indeed. I look forward to tracking each others blogs.

    Thanks for everything,

    – Chris

  74. samson (11 comments) says:

    @ray & ricky, you are welcome!

    BTW yesterday my app was approved! It’s still the “ad in the tableheaderview and can be scrolled away” design. My final solution to avoid row shifting cause by ad bar hide/show is to use a static logo bar when ad is not available. Looks like app reviewers are fine with it as long as a blank ad bar is not shown.

    Now the iAd fill rate is really low, like 5-7% for me. But each click generates $1.x income. Not bad. eCPM shows $300+ (note: this doesn’t mean you’ll get $300 for every 1000 impressions. It’s a reversed calculated value by dividing your income of all clicks and impressions by number of impressions. It can’t be used to predict or calculate your income. Took me a while to understand this).

    I believe the rate will decrease as more apps support iAd. Also there are only 2 ads right now, dove + men and nissan leaf car. I’m kind of bored to see them everyday. Hope more advertisers join.

  75. Bob Schoenburg (6 comments) says:

    Does iAds work on ALL iPods or only those with certain specs?

  76. Philip (2 comments) says:

    Hi Ray,

    I have a question. When I put this into my app, I get a warning on:
    [self createAdBannerView]; in the viewDidLoad method,
    and the same warning on this line:
    [self refresh]; in the viewWillAppear method.

    The warning says, “‘iCharacterViewController’ may not respond to ‘-refresh’” and

    ‘iCharacterViewController’ may not respond to ‘-createAdBannerView’.

    The app crashes whenever it is opened…

    Help? Anyone?

  77. Ray Wenderlich (399 comments) says:

    @Chris: Hm, the first thing I’d check is if the autosizing properties are set up properly for your RootViewController’s view, subview, and table view controller. Yeah autosizing can be a pain to get working right sometimes…

    @samson: Awesome, glad your app got approved! Fill rate is 15% on one of my apps, and 7% on another. I’m not sure why there’s a difference! But either way agreed that is pretty low eh? I’ve heard some people are showing ads from other networks when iAds aren’t available?

    @Bob: iAds only work on iOS 4.0+.

    @Philip: That warning means the compiler can’t find the refresh and createAdBannerView methods in your class. Have you added those methods? If all else fails, try stepping through with your debugger.

  78. Jim Murff (6 comments) says:

    Thanks again Ray. You are awesome! I made some slight changes if you want ads to appear at the bottom.
    Thought I’d share it to save someone else the trouble.

    I get the warning too about -shouldAutorotateToInterfaceOrientation:. I researched it on developers forum. It’s a known bug in the iAd framework.

    Here is code with ability to to do Bottom display. Just use the define to activate relevant code:

    // Comment out or undef to use iAds at top of screen.
    #define __BOTTOM_iAD__

    -(void)createAdBannerView
    {
    Class classAdBannerView = NSClassFromString(@"ADBannerView");
    if (classAdBannerView != nil)
    {
    self.adBannerView = [[[classAdBannerView alloc]
    initWithFrame:CGRectZero] autorelease];
    [adBannerView setRequiredContentSizeIdentifiers:[NSSet setWithObjects:
    ADBannerContentSizeIdentifier320x50,
    ADBannerContentSizeIdentifier480x32, nil]];

    if (UIInterfaceOrientationIsLandscape([UIDevice currentDevice].orientation))
    {
    [adBannerView setCurrentContentSizeIdentifier:ADBannerContentSizeIdentifier480x32];
    }
    else
    {
    [adBannerView setCurrentContentSizeIdentifier:ADBannerContentSizeIdentifier320x50];
    }

    #ifdef __BOTTOM_iAD__
    // Banner at Bottom
    CGRect cgRect =[[UIScreen mainScreen] bounds];
    CGSize cgSize = cgRect.size;
    [adBannerView setFrame:CGRectOffset([adBannerView frame],
    0, cgSize.height + [self getBannerHeight])];
    #else
    // Banner at the Top
    [adBannerView setFrame:CGRectOffset([adBannerView frame],
    0, -[self getBannerHeight])];
    #endif

    [adBannerView setDelegate:self];

    [self.view addSubview:adBannerView];
    }
    }

    -(void)fixupAdView:(UIInterfaceOrientation)toInterfaceOrientation
    {
    if (adBannerView != nil)
    {
    if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation))
    {
    [adBannerView setCurrentContentSizeIdentifier:ADBannerContentSizeIdentifier480x32];
    }
    else
    {
    [adBannerView setCurrentContentSizeIdentifier:ADBannerContentSizeIdentifier320x50];
    }

    [UIView beginAnimations:@"fixupViews" context:nil];
    if (adBannerViewIsVisible)
    {
    CGRect adBannerViewFrame = [adBannerView frame];
    adBannerViewFrame.origin.x = 0;
    #ifdef __BOTTOM_iAD__
    CGRect cgRect =[[UIScreen mainScreen] bounds];
    CGSize cgSize = cgRect.size;
    adBannerViewFrame.origin.y = cgSize.height -
    [self getBannerHeight:toInterfaceOrientation];
    #else
    adBannerViewFrame.origin.y = 0;
    #endif
    [adBannerView setFrame:adBannerViewFrame];

    CGRect contentViewFrame = contentView.frame;
    #ifdef __BOTTOM_iAD__
    contentViewFrame.origin.y = 0;
    #else
    contentViewFrame.origin.y = [self getBannerHeight:toInterfaceOrientation];
    #endif
    contentViewFrame.size.height = self.view.frame.size.height -
    [self getBannerHeight:toInterfaceOrientation];
    contentView.frame = contentViewFrame;
    }
    else
    {
    CGRect adBannerViewFrame = [adBannerView frame];
    adBannerViewFrame.origin.x = 0;
    #ifdef __BOTTOM_iAD__
    CGRect cgRect =[[UIScreen mainScreen] bounds];
    CGSize cgSize = cgRect.size;
    adBannerViewFrame.origin.y = cgSize.height;
    #else
    adBannerViewFrame.origin.y = 0;
    #endif
    [adBannerView setFrame:adBannerViewFrame];

    CGRect contentViewFrame = contentView.frame;
    contentViewFrame.origin.y = 0;
    contentViewFrame.size.height = self.view.frame.size.height;
    contentView.frame = contentViewFrame;
    }

    [UIView commitAnimations];
    }
    }
  79. Ray Wenderlich (399 comments) says:

    @Jim: Awesome, thanks so much for sharing, I’m sure it will be useful to others as well!

  80. JDizzle (1 comments) says:

    I also recently submitted an app in the store that implements the ad banner view. I guess I just want to reiterate what samson said:

    ** Apple REQUIRES that you hide your banner ad if no ads are being served, FOR NOW. This is NOT explicitly mentioned in the documentation. If you don’t do this, the app will get rejected. **

    And yes, there aren’t that many ads being served yet. I think that’s part of the reason why they are rejecting apps that don’t hide the banner view (since there aren’t that many ads being served yet, to begin with).

  81. Ricky (5 comments) says:

    Yes, Apple wants that, and i think he’s right. I need to use the iAd banner in the bottom of the screen, so tomorrow i’ll try the code Jim Murff, thank you so much for sharing :)
    Sorry for the Off Topic, but my app makes an export of the datas on some files with a personalized extension, but i don’t know how set the document icon for these files. I’ve looked all around the developer site and the web without find a solution, somebody of could help me? :(

  82. Adrian Thompson (1 comments) says:

    Great tutorial, not sure if this has already been asked/answered but what are your thoughts on iAd placement within an app?

    Some views require as much screen real-estate as possible for your app and adding an iAd is simple too much. Do you think adding an iAd within an ‘about app’ view for example would be frowned upon by Apple? I know to benefit the developer its about getting an much exposure to the ad as possible but wondered what peoples thoughts were on this.

    Thanks

    Adrian

  83. ltgbau (2 comments) says:

    My app is ready for sales but iAds isn’t good as I think before, revenue is worse than AdMob :(

  84. Gucman (1 comments) says:

    Hi
    The artice is great. I finally managed to integrate iAd into my game. Everything runs smoothly (it works also on pre 4.0 without apps) but I have problem with clicking the ad.
    When I click the ad the correct callback is called as it should be (implementation is below)
    [code] - (BOOL)bannerViewActionShouldBegin:(id)banner willLeaveApplication:(BOOL)willLeave
    {
    GLOG(@"bannerView Begin");
    if (!willLeave)
    [game pauseAndStopMusic];
    return YES;
    }[/code]
    But nothing changes (except game is paused and music stops) in displaying ad (I can’t see no “action”). Also there should be called other callback some time after clicking ad informing its action is over. I have implemented it like so:
    [code]
    - (void)bannerViewActionDidFinish:(id)banner
    {
    GLOG(@"bannerView End");
    [game continueWithMusic];
    }[/code]
    It is never called. Have You any idea why it is not called? I have tested it on the simulator and real device (iPod Touch) and the results are the same. Is it possible that those “test ads” are bad? I belive there is no possibility to have real ads during development – only distributed apps via AppStore have real ads, am I right?

  85. Ray Wenderlich (399 comments) says:

    @JDizzle: Good to know that hiding is actually required – thank goodness I happened to do it that way when I submitted my app! :]

    @Ricky: Have you tried setting the document icon for your app according to this guide?

    http://developer.apple.com/iphone/library/documentation/userexperience/conceptual/mobilehig/IconsImages/IconsImages.html

    @Adrian: AFAIK it’s up to you as a developer where you put your ads, so I don’t think there would be any problem putting it in an about view. But of course, I doubt users would go there much, which means you wouldn’t make as much revenue…

    @itgbau: Yeah will be interesting to see how this plays out in the long run…

    @Gucman: Interesting, I haven’t seen that problem. I’d try the sample project above and make sure the ads show up for you OK there – if they do it may be something specific to your project that is preventing the ads from displaying.

  86. Bill Seyler (1 comments) says:

    Such a great tutorial. I was able to retrofit my app in about 2 hours (and I had to do the rework of several table views)!

    Great job Ray!

    Bill

Leave a Comment

I'd love to hear your thoughts!