Add a navigation button
This will pickup where Create a view left off. So you can pick it up from there and download the source. You probably don’t even need the source but grab it if you wish
Now when you create a new view, in that view you might want to place a button in the navigation bar. This is pretty easy to do. So you want to open up the sub-view controller. In my example I called it BrowseViewController. So I will open up BrowseViewController.m and look for the loadView function. In my example you will see it commented ( /* */ ) out. For example.
/*
Implement loadView if you want to create a view hierarchy programmatically
- (void)loadView {
}
*/
You will notice it is all green. To make it that function work just remove the /* and */ (don’t forget the comment text also) around it so now it looks like
- (void)loadView {
}
Now we want to add a button. So we just add in some code.
UIBarButtonItem *addButton = [[[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Something", @"") style:UIBarButtonItemStyleBordered target:self action:@selector(doSomething)] autorelease]; self.navigationItem.rightBarButtonItem = addButton;
So you see we are creating a button with text Something. We are also setting the style as a button with a border. the target is the object it will call on button press and the action is the function. We are auto releasing it also so we don’t have to do a [addButton release]; If we were to add that the application would seg fault.
Now you have to create the function doSomething
- (void)doSomething {
}
There ya go.. all done. The final product should look like this.
You can grab the source code here.
JSON Error
If you want to show an error if the host is down it is pretty easy. This thinks you have read the other JSON posts. So look at the viewDidLoad function.
This is a continuation of Custom UITableViewCell
We are just adding a if {} else {} and if the data is nil, show an error.
- (void)viewDidLoad {
// Add the following line if you want the list to be editable
// self.navigationItem.leftBarButtonItem = self.editButtonItem;
// init the url
NSURL *jsonURL = [NSURL URLWithString:@"http://iphone.zcentric.com/test-json2.php"];
NSString *jsonData = [[NSString alloc] initWithContentsOfURL:jsonURL];
if (jsonData == nil) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Webservice Down" message:@"The webservice you are accessing is down. Please try again later." delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil];
[alert show];
[alert release];
}
else {
// converting the json data into an array
self.jsonArray = [jsonData JSONValue];
}
// releasing the vars now
[jsonURL release];
[jsonData release];
}
You can test this by simply changing the hostname to like iphone2.zcentric.com and it will show an error like the following.
Custom UITableViewCell
Make sure you have JSON Framework installed first.
This is a continuation of More Complex JSON endpoint
The reason why OOP is so powerful is that you can subclass everything to make it work or look just how you want it. The default look of a UITableViewCell is just one line of text. If you want to add some text below it you need to subclass that. So that is what we will do right now.
The first thing we want to do is create a new subclass in your project. You should know how to do this already. File -> New File and select a UITableViewCell subclass. I have named mine ImageCell
Now you can see we have the title in nice big letters and the URL to the image under in in smaller letters and also in grey
I am just going to paste all the code and I have made some comments to show you what I added
First your ImageCell.h should look like
#import
@interface ImageCell : UITableViewCell {
// adding the 2 labels we want to show in the cell
UILabel *titleLabel;
UILabel *urlLabel;
}
// these are the functions we will create in the .m file
// gets the data from another class
-(void)setData:(NSDictionary *)dict;
// internal function to ease setting up label text
-(UILabel *)newLabelWithPrimaryColor:(UIColor *)primaryColor selectedColor:(UIColor *)selectedColor fontSize:(CGFloat)fontSize bold:(BOOL)bold;
// you should know what this is for by know
@property (nonatomic, retain) UILabel *titleLabel;
@property (nonatomic, retain) UILabel *urlLabel;
@end
There really isn’t any magic going on in the header. So now open up the ImageCell.m file and my contents are as follows.
#import "ImageCell.h"
@implementation ImageCell
// we need to synthesize the two labels
@synthesize titleLabel, urlLabel;
- (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) {
// Initialization code
// we need a view to place our labels on.
UIView *myContentView = self.contentView;
/*
init the title label.
set the text alignment to align on the left
add the label to the subview
release the memory
*/
self.titleLabel = [self newLabelWithPrimaryColor:[UIColor blackColor] selectedColor:[UIColor whiteColor] fontSize:14.0 bold:YES];
self.titleLabel.textAlignment = UITextAlignmentLeft; // default
[myContentView addSubview:self.titleLabel];
[self.titleLabel release];
/*
init the url label. (you will see a difference in the font color and size here!
set the text alignment to align on the left
add the label to the subview
release the memory
*/
self.urlLabel = [self newLabelWithPrimaryColor:[UIColor blackColor] selectedColor:[UIColor lightGrayColor] fontSize:10.0 bold:NO];
self.urlLabel.textAlignment = UITextAlignmentLeft; // default
[myContentView addSubview:self.urlLabel];
[self.urlLabel release];
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
/*
this function gets in data from another area in the code
you can see it takes a NSDictionary object
it then will set the label text
*/
-(void)setData:(NSDictionary *)dict {
self.titleLabel.text = [dict objectForKey:@"title"];
self.urlLabel.text = [dict objectForKey:@"img"];
}
/*
this function will layout the subviews for the cell
if the cell is not in editing mode we want to position them
*/
- (void)layoutSubviews {
[super layoutSubviews];
// getting the cell size
CGRect contentRect = self.contentView.bounds;
// In this example we will never be editing, but this illustrates the appropriate pattern
if (!self.editing) {
// get the X pixel spot
CGFloat boundsX = contentRect.origin.x;
CGRect frame;
/*
Place the title label.
place the label whatever the current X is plus 10 pixels from the left
place the label 4 pixels from the top
make the label 200 pixels wide
make the label 20 pixels high
*/
frame = CGRectMake(boundsX + 10, 4, 200, 20);
self.titleLabel.frame = frame;
// place the url label
frame = CGRectMake(boundsX + 10, 28, 200, 14);
self.urlLabel.frame = frame;
}
}
/*
this function was taken from an XML example
provided by Apple
I can take no credit in this
*/
- (UILabel *)newLabelWithPrimaryColor:(UIColor *)primaryColor selectedColor:(UIColor *)selectedColor fontSize:(CGFloat)fontSize bold:(BOOL)bold
{
/*
Create and configure a label.
*/
UIFont *font;
if (bold) {
font = [UIFont boldSystemFontOfSize:fontSize];
} else {
font = [UIFont systemFontOfSize:fontSize];
}
/*
Views are drawn most efficiently when they are opaque and do not have a clear background, so set these defaults. To show selection properly, however, the views need to be transparent (so that the selection color shows through). This is handled in setSelected:animated:.
*/
UILabel *newLabel = [[UILabel alloc] initWithFrame:CGRectZero];
newLabel.backgroundColor = [UIColor whiteColor];
newLabel.opaque = YES;
newLabel.textColor = primaryColor;
newLabel.highlightedTextColor = selectedColor;
newLabel.font = font;
return newLabel;
}
- (void)dealloc {
// make sure you free the memory
[titleLabel dealloc];
[urlLabel dealloc];
[super dealloc];
}
@end
So now we can save that file and open up RootViewController.m
We need to add in a import
#import "ImageCell.h"
Now go down to the cellForRowAtIndexPath function. Right now we have a UITableViewCell *cell call to init the cell, we need to change that since we are using our new ImageCell class. So replace that line with
ImageCell *cell = (ImageCell *)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
Also we need to replace the cell = line in the if (cell == nil) if statement
cell = [[[ImageCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
Now remove the cell.text line and replace it with the following
NSDictionary *itemAtIndex = (NSDictionary *)[self.jsonArray objectAtIndex:indexPath.row]; [cell setData:itemAtIndex];
Now you can build and go and you should get the following
As you can see you now have the title in nice big letters and the URL in smaller gray letters below the title. As always you can grabt he source code here.
More Complex JSON endpoint
Make sure you have JSON Framework installed first.
This is a continuation from First JSON Iphone application
So the last entry went into describing how we do very basic JSON. Now lets get into something a bit more complex. Here is what my array looks like in PHP.
$array = array(
array(
'title' => 'Brown Dog',
'img' => 'http://iphone.zcentric.com/files/1.jpg',
),
array(
'title' => 'Black Dog',
'img' => 'http://iphone.zcentric.com/files/2.jpg',
),
array(
'title' => 'Two Dogs',
'img' => 'http://iphone.zcentric.com/files/3.jpg',
),
array(
'title' => 'Girl',
'img' => 'http://iphone.zcentric.com/files/4.jpg',
),
);
So you can see it is a multi-dimensional array in PHP. In the main array are 4 sub-arrays that each have a title and a img key. The title is the title of the image and the img is a URL to the image. So we can pretty much use our example almost line for line to do this more complex array now
So first open up RootViewController.m and find the line where we inited the JSON URL. All you are doing is changing the endpoint
NSURL *jsonURL = [NSURL URLWithString:@"http://iphone.zcentric.com/test-json2.php"];
Before it was using test-json.php and now we are using test-json2.php.
Now go down to cellForRowAtIndexPath and remove the cell.text line that is there now. You want the cell text to look like this now
NSDictionary *itemAtIndex = (NSDictionary *)[self.jsonArray objectAtIndex:indexPath.row]; cell.text = [itemAtIndex objectForKey:@"title"];
So you can see what we are doing is grabbing a dictionary off the array at a certain index and then in that dictionary we are putting the title in for the cell text.
If you build and go now you should see this.
As always you can download the source here.
First JSON Iphone application
So now that we have the basics of installing JSON to our project, lets make a test application.
I started by creating a new project and used the Navigation Controller Template.
The first thing you want to do is setup your JSON environment as explained in the previous post and again you can visit the link above for instructions on how to do that.
Once it is setup you are ready to code. So the first thing is open up RootViewController.m and we need to import the JSON header so we can access it.
#import <JSON/JSON.h>
You need to synthesize the jsonArray
@synthesize jsonArray;
Now you want to edit the viewDidLoad function and make it look like this
- (void)viewDidLoad {
// Add the following line if you want the list to be editable
// self.navigationItem.leftBarButtonItem = self.editButtonItem;
// init the url
NSURL *jsonURL = [NSURL URLWithString:@"http://iphone.zcentric.com/test-json.php"];
/*
loading the contents of the URL into a string. At this point we should hit
the json endpoint and put the contents of it in jsonData.
*/
NSString *jsonData = [[NSString alloc] initWithContentsOfURL:jsonURL];
/*
we can verify the contents by looking at a NSLog if we open the console
*/
//NSLog(jsonData);
// converting the json data into an array
self.jsonArray = [jsonData JSONValue];
// this will return the array.. in the console it will still look a bit like json
// but that is a array object.
//NSLog(@"%@", jsonArray);
// this will give the current count
//NSLog(@"count is: %i", [self.jsonArray count]);
// releasing the vars now
[jsonURL release];
[jsonData release];
}
I hope I gave good comments in the code. Basically what we are doing is loading a url and placing the contents into a string and then using json to turn it into an array object that we can use later.
Now my array in PHP looks like this
$array = array(
0 => 'http://iphone.zcentric.com/files/1.jpg',
1 => 'http://iphone.zcentric.com/files/2.jpg',
2 => 'http://iphone.zcentric.com/files/3.jpg',
3 => 'http://iphone.zcentric.com/files/4.jpg',
);
I use 0-3 as keys to make it easy to load the values into a new row.
Now we need to tell the application how many rows we have
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [jsonArray count];
}
Then in the cellForRowAtIndexPath function above the return cell we want to set the text.
cell.text = (NSString *)[self.jsonArray objectAtIndex:indexPath.row];
Now we want to free the memory
- (void)dealloc {
[jsonArray dealloc];
[super dealloc];
}
Now we are done with this file so save it and close it and open up RootViewController.h. You want it to look like this
#import
@interface RootViewController : UITableViewController {
NSMutableArray *jsonArray; // added this
}
@property (nonatomic, retain) NSMutableArray *jsonArray; // added this
@end
You should know to do this by now.
Once you are done you should be able to compile your application and see 4 rows. Also feel free to use my test-json.php endpoint since I know it works.
The source code is right here. Have fun!
Install json-framework
If you are attempting to write a Iphone application to talk to a webservice, JSON is one of the best ways to go. Now unfortunately there is no native support in the Iphone SDK for JSON. There is a project called json-framework that provides a framework called json-framework.
There is little documentation on how to install it though. Recently I found a news group posting on how to do it along with a dmg! I have mirrored the dmg file just in case it gets removed from that posting.
So here are some instructions on how to install it.
- Download the dmg file
- Make a directory called SDKs in your ~/Library/ directory. So if you username is bob. it would be /Users/bob/Library/SDKs
- Copy the JSON directory to SDKs so you should have a directory called /Users/bob/Library/SDKs/JSON which has iphoneos.sdk and iphonesimulator.sdk directories inside it.
- In Xcode load up your project and go to Project -> Edit Project Settings. You will see a box like this
- At the top you will see Additional SDKs. Double click that and you will see
- Now click the + sign and it will prompt you to add a directory. You will enter the following line
$HOME/Library/SDKs/JSON/$(PLATFORM_NAME).sdk
- Now you need to add in the linker options. So now look for Other Linker Flags and add in the following
-ObjC -ljson
- Now you can import the <JSON/JSON.h> header file and begin coding.
I have also mirrored their test application they built in the news group post. So if you download that and make the project changes there, you should be able to compile touchJSON into your application.
Add a back button
Now that we have two views nicely done, we want to be able to navigate back to the main view. We do this with a back view in navigation bar in the top.
Open up RootViewController.m and look for the viewDidLoad function. You want to add the following line to it.
self.title = @"Photoblog";
Then when you click on browse, it will show Photoblog in the back navigation.
Create a view
So today we will create our first view. This takes over where Create a new class left off. So you can grab the source code from there if you need to. I also edited the arrows for the rows by following this tutorial I created.
Also in the DataController.m class I am changing the names to something more meaningful.
[menuArray addObject:[NSDictionary dictionaryWithObjectsAndKeys: NSLocalizedString(@"Browse", @""), @"title", nil, nil]]; [menuArray addObject:[NSDictionary dictionaryWithObjectsAndKeys: NSLocalizedString(@"Options", @""), @"title", nil, nil]];
Now we want to create a new view. So double click on RootViewController.xib and it will launch IB (Interface Builder). We don’t need to edit or change anything here, it was just a quick way of loading IB up. So now lets go to File -> New and choose a View.
Now to make things simple, make sure the Library is opened by choosing Tools -> Library. Then select the Inputs & Values and drag a label over to the view and drop it there. So now our view looks like this
Now we want to save the view as a .xib file which for some reason Apple calls a NIB. I don’t know why the file extension is different.
So choose File -> Save. I am doing this just for the Browse option, so I am calling this the BrowseView. Make sure you save it in the directory that you use for your project. If you do it will prompt you if you want to add it to the project and make sure you do.
Now if you view Xcode it will save it in the right hand panel of the left hand menu. I like to drag the BrowseView.xib into the Resources directory.
Now we need to add a new controller to the project. Remember in MVC every view must have a controller. A controller will show the view and handle everything that is on the view. So if you add a button, the controller will handle what that button does when someone clicks it.
So in Xcode choose File -> New File and select the UIViewController subclass. You will want to give it the name of BrowseViewController.m
Now that the new class is in your project lets write some code!
Open up DataController.m and look for the createData function. Now we listed 2 nils in our dictionary, we want to change this but just for browse right now. So now the createData looks like this.
-(void) createData {
menuArray = [[NSMutableArray alloc] init];
BrowseViewController *controller = [[BrowseViewController alloc] initWithNibName:@"BrowseView" bundle:nil];
[menuArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:
NSLocalizedString(@"Browse", @""), @"title",
controller, @"view",
nil]];
[controller release];
[menuArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:
NSLocalizedString(@"Options", @""), @"title",
nil,
nil]];
}
So you can see above we init the browseController
In the same file we need to include the new controller so at top put
#import "BrowseViewController.h"
Open up RootViewController.h
We want to import the new class we just created. So you want to top to look like
#import #import "DataController.h" #import "BrowseViewController.h"
Now we need to init the controller in the class So your class declaration should look like this with the new BrowseViewController in it.
@interface RootViewController : UITableViewController {
DataController *dataController;
BrowseViewController *bvController; // added this
}
@property (nonatomic, retain) DataController *dataController;
@property (nonatomic, retain) BrowseViewController *bvController; // added this
@end
Now open up RootViewController.m and add a synthesize for the new controller.
@synthesize dataController, bvController;
Now look for the function didSelectRowAtIndexPath. It should be blank now make i look like this
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic
NSDictionary *itemAtIndex = (NSDictionary *)[dataController getItem:indexPath.row];
if ([itemAtIndex objectForKey:@"view"] != nil) {
self.bvController = [itemAtIndex objectForKey:@"view"];
[self.navigationController pushViewController:self.bvController animated:YES];
}
}
So there we check if the view is nil, if it is not nil then load the view. So when we are ready to build our application we can click browse and it will go to that view but if we click options it does nothing.
Now we want to release the memory
- (void)dealloc {
[dataController release];
[bvController release]; // added this
[super dealloc];
}
Now that all the code is done, we have to connect things in IB. So double click on the BrowseView.xib file and IB will launch. Now make sure you have the File’s Owner selected and in the Class Identity (Tools -> Identities Inspector) make sure the class is BrowseViewController is selected. It should be listed in the drop down.
Now select the View and make sure View Connections is open (Tools -> Connections Inspector)
You will just see this
So click on the circle on the left of New Referencing Outlet and hold the click down. As you move your mouse around move your mouse over the File’s Owner
So when you release the mouse over File’s Owner. A box will popup to choose View. Make sure you click on the View. So now it will look like this
Now save your xib. File -> Save and in Xcode build and go. So the main screen looks like
If you click on browse you should get
Still nothing happens if we click on Options which is what we wanted. As always you can grab the source here.
Add Arrows to rows
I found a something a bit late what would of been nice to cover before. I guess it is better late then never right?
In offical IPhone apps there are arrows on each row you can click on that mean you can click here to users. We want to add these nice little arrows.
So open up RootViewController.m and look for the function cellForRowAtIndexPath and in the if (cell == nil) {} block you want to add a link for accessoryType so it looks like
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
Your lists should now look like
No source code for this since it was just a one line change
Create a new class
So now that we have a working navigation based application. (download source here) We now want to move the data outside to a seperate class. We do this so we can use the data around the application and not just RootViewController class.
Do you get why? Its so we can use the data in the next view we will get into later.
So the first thing you want to do is create a data holder class. I will call it DataController.m/.h. So the first thing you want to do is right click on the Classes folder on the left hand menu. Then go Add -> New File.
You want to choose the NSObject subclass.
Give it the name DataController. Now that the file is created you will see you have two new files in Classes called DataController.h and DataController.m.
Now we want to move the array over. So the DataController.h looks like
#import
@interface DataController : NSObject {
NSMutableArray *menuArray;
}
@property (nonatomic, copy, readwrite) NSMutableArray *menuArray;
- (NSInteger)getCount;
- (id) getItem:(NSInteger)index;
- (void)createData;
@end
So you can see we moved the menuArray array in and we also added a new function called getCount. Later we will use that to return the number of items in the menuArray
So open up the DataController.m. So below @implementation DataController we want to synthesize the array so now your .m file looks like
@implementation DataController @synthesize menuArray; @end
You use the @synthesize keyword to tell the compiler that it should synthesize the setter and/or getter methods for the property if you do not supply them within the @implementation block.
So now we need to init the data in the class.
So we need to add an init function. Below the @synthesize you want to add something that looks like
- (id)init {
if (self = [super init]) {
[self createData];
}
return self;
}
So now we need to create a createDemo function in the class. What I am basically doing is moving all the array items from the RootViewController over to this function.
-(void) createData {
menuArray = [[NSMutableArray alloc] init];
[menuArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:
NSLocalizedString(@"Hello World", @""), @"title",
nil,
nil]];
[menuArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:
NSLocalizedString(@"Hello Universe", @""), @"title",
nil,
nil]];
}
Now we want to return the count of the current array to an outside class. So we add in this function.
-(NSInteger)getCount {
return [menuArray count];
}
We also need to return the item. Since we are displaying these items it will help to return an item at a given index. So add this function in also
- (id)getItem:(NSInteger)index {
return [menuArray objectAtIndex:index];
}
So what that function says is when called it expects a index to come in as a NSInteger type. It will use that to get the item at that index in the menuArray and it will return it.
Now you want to remove all the instances of menuArray in the RootViewController.h and RootViewController.m. Since we are not using it anymore.
So now back to the RootViewController.m. We need to initialize our new DataController class, so in the viewDidLoad function we want to add the following.
DataController *controller = [[DataController alloc] init];
self.dataController = controller;
[controller release];
Now in the numberOfRowsInSection, we want to return the current count of the menuArray that is now inside the DataController. This is why we made the getCount function, so we can get that count.
So the function should now look like this
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [dataController getCount];
}
Now look for the cellForRowAtIndexPath function and you need to add in setting up the text
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = @"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
}
NSDictionary *itemAtIndex = (NSDictionary *)[dataController getItem:indexPath.row];
cell.text = [itemAtIndex objectForKey:@"title"];
// Set up the cell
return cell;
}
So you can see there, we are grabbing the dictionary from the array and then using the title key to grab out the title for each row at a certain index.
As always you can grab the source code here.


















