SQLite Tutorial - Adding data
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 Tutorial - Deleting data
- SQLite Tutorial - Inserting data into SQLite database
- SQLite Tutorial - Saving images in the SQLite database
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
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.
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?
@Marin, @Shawn: Thanks guys for the kind words! :]
@Abhinav: iAds are only available on iOS4+, so they will not work on iPad yet!
Ok. So, When it will be available on iPad..??
Can I upgrade iPad to 4.0?
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 ;)
@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!
Hey Ray,
Thanks for the tutorial.
Could you explain how to implement it in cocos2d?
Thanks!!
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
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
@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
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
@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”.
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.
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??
@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.
@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!
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
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
@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
@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!
@Ray: it has been being a big discussion on devforum.apple.com
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.
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….
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!
@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?
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
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!
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
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!
@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.
Hi Ray,
The problem indeed got fixed when I declared the items in the .h files as needed.
Thanks
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.
Hi Ray!
Thanks for the tut, I’ve just intergrated iAds to my app. I’ll test and submit it soon.
Thanks again!
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
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
Anyone had tried to integrate iAds into three20 ? To be more specific, how about TTPhotoView ?
@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.
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
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.
@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.
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
@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.
Special thanks to Thang Tran, i am going to upload my app to appstore.
@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.
Uploaded my app, waiting for review. Thanks to all for the help and comments.
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.
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.
@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
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
@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?
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?
@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.
@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.
@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.
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
@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.
@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! :]
@Ray: yeah, you are right! This is the key point no matter if your app is free for paid :)
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.
@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.
@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)
@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.
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 :)
@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 $$!
Hi Ray,
Thanks to you, my app is now up.
http://itunes.apple.com/us/app/icoin-free/id381816540?mt=8
@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…
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 :)
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?
@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
Thanks samson, you are really really helpful!! :)
@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 :]
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
@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.
Does iAds work on ALL iPods or only those with certain specs?
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?
@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.
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:
@Jim: Awesome, thanks so much for sharing, I’m sure it will be useful to others as well!
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).
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? :(
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
My app is ready for sales but iAds isn’t good as I think before, revenue is worse than AdMob :(
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?
@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.
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