View Images like Photos.app

Make sure you have JSON Framework installed first.

This is a continuation of Using threads for a JSON request

So right now we have four rows in our table. Each row has a image. So what if we want to view all the images at once. I really like viewing images at my own time. So I don’t want to use a timer and have them switch. If i want to skip a image I want to skip and if I want to take time to look, I want to look.

I really like how Apple has their Photos.app when you view images. You can slide your finger around to move to new images. So lets replicate this.

So open up your project and we need to create a new view to show our images. So right click on Resources -> Add -> New File. Then you want to create a user Interface and View XIB file. We want to call it picView.xib

So now we want to make a controller for the picView. So right click on Classes -> Add -> New File. And we want to create a new UIViewController subclass. We want to call it picViewController.m

Now we need to create a new a new outlet and a new method to init our view. So double click on picViewController.h and we want it to look like the following.

#import <UIKit/UIKit.h>

@interface picViewController : UIViewController {
	IBOutlet UIImageView *image;
}

- (id)initWithImage:(NSString *)url;

@end

Now we are creating a new init method. Normally we did a initWithNibName and then pass in the url to the image. Lets shake things up and and init our view with a custom method and load the nib in the custom function.

So open up picViewController.m and our initWithImage method will look like this

- (id)initWithImage:(NSString *)url {
    // loading the nib here
    if (self = [super initWithNibName:@"picView" bundle:nil]) {
		// grabbing the image and setting it to our imageview
		image.image = [UIImage imageWithData: [NSData dataWithContentsOfURL: [NSURL URLWithString: url]]];
    }
    return self;
}

Now save the file and double click on picView.xib and load up IB. So we want to add a imageview to our whole view so it looks like the following.

Then we need to change File Owner’s type to picViewController.

Now make the connection from the image outlet we created to the imageview on the view. Also make sure you connect the view to the File’s Owner.

Now that we have our picViewController all done lets create a nice button on the navigation bar for an option to show all images. So open up RootViewController.m and in the viewDidLoad method we add the following.

	UIBarButtonItem *showButton = [[[UIBarButtonItem alloc]
			initWithTitle:NSLocalizedString(@"Show All", @"")
			style:UIBarButtonItemStyleBordered
			target:self
			action:@selector(showAll)] autorelease];
	self.navigationItem.rightBarButtonItem = showButton;

Don’t worry we will create our showAll method later.

Now we need to create a new controller and view for the main view that will control the scrolling of our images.

So right click on Resources -> Add -> New File. Then you want to create a user Interface and View XIB file. We want to call it scrollView.xib

So now we want to make a controller for the scrollView. So right click on Classes -> Add -> New File. And we want to create a new UIViewController subclass. We want to call it scrollViewController.m

Now we need to create a new outlet for our scroll view. So open up scrollViewController.h and add in the following.

IBOutlet UIScrollView *scrollView;

Now save the file and double click on scrollView.xib. Now we want to create a new scroll view in the view. So the way to do this is drag a new UIView on the view that is already there and then change the type to a UIScrollView. It should look like the following.

Now we want to change the File Owner’s type to scrollViewController so it looks like the following

Then we want to create the outlet from the File Owner to the scroll view and then connect the view to the main view.

Now the one thing we want to make sure is our main view isn’t too high. If it is the user will be able to move the view up and down and that isn’t the desired effect. We only want them to flick right and left. 

So click on the View and you want to make the height 460. We will get rid of the navigation bar in code when this loads. If you want to keep the navigation bar you have to make the height even less.

Save the xib file and now lets get to some code.

So the first thing we want to is setup our picViewControntroller class. So open up picViewController.h and we want it to look like the following

@interface picViewController : UIViewController {
	IBOutlet UIImageView *image;
	UIImage *img;
}

@property (nonatomic, retain) UIImage *img;

- (id)initWithImage:(NSString *)url;

@end

So we have our image view outlet. We also have a UIImage that will contain the passed in image from our scroll view controller. We create a new init function to easily pass in the image on init. In that function we will call the init nib method

So now open up viewPicController.m. That whole file should look like this

@implementation picViewController

@synthesize img;

- (id)initWithImage:(NSString *)url {

	// loading the nib here
    if (self = [super initWithNibName:@"picView" bundle:nil]) {
		// grabbing the image and setting it to our imageview
		img = [UIImage imageWithData: [NSData dataWithContentsOfURL: [NSURL URLWithString: url]]];
    }
    return self;
}

- (void)viewDidLoad {
	image.image = img;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
	// Return YES for supported orientations
	return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

- (void)didReceiveMemoryWarning {
	[super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
	// Release anything that's not essential, such as cached data
}

- (void)dealloc {
	[super dealloc];
}

@end

You can see on the new init, we load the nib and then set the passed in image to img. On our viewDidLoad we set the image to the imageview.

So our picViewController is now complete. Lets move on to the scrollViewController where most of the fun is happening.

So open up scrollViewController.h and make it look like the following. You should already have added he outlet for the scrollview

@interface scrollViewController : UIViewController  {
	IBOutlet UIScrollView *scrollView;
	NSMutableArray *viewControllers; // will contain all the picViewControllers
	NSMutableArray *jsonArray; // will get passed in a JSONarray
}

@property (nonatomic, retain) NSMutableArray *viewControllers;
@property (nonatomic, retain) NSMutableArray *jsonArray;

- (id)initWithJSON:(NSMutableArray *)array; // init method to get the json array
- (void)loadPage:(int)page;  // loads a new picViewController

@end

You will see we need to include a scrollview delegate also. This is so we can handle the movements of the scrollview.

Now save that and open up scrollViewController.m and we need to add our inits in. So yo will need to add the following.

- (id)initWithJSON:(NSMutableArray *)array {
	if (self = [super initWithNibName:@"scrollView" bundle:nil]) {
		// Initialization code
		self.jsonArray = [[NSMutableArray alloc] init];
		self.jsonArray = array;
	}
	return self;
}

Now we need to create a loadPage method. What this method is doing is it is saying load the picViewController into the viewControllers array if it is not already there. We will create this array a bit later so don’t worry about it not being there.

- (void)loadPage:(int)page {
    if (page < 0) return;
    if (page >= [self.jsonArray count]) return;

    // replace the placeholder if necessary
    picViewController *controller = [viewControllers objectAtIndex:page];
    if ((NSNull *)controller == [NSNull null]) {

		// grabbing the image
		NSDictionary *itemAtIndex = (NSDictionary *)[self.jsonArray objectAtIndex:page];

        controller = [[picViewController alloc] initWithImage:[itemAtIndex objectForKey:@"img"]];
        [viewControllers replaceObjectAtIndex:page withObject:controller];
        [controller release];
    }

    // add the controller's view to the scroll view
    if (nil == controller.view.superview) {
        CGRect frame = scrollView.frame;
        frame.origin.x = frame.size.width * page;
        frame.origin.y = 0;
        controller.view.frame = frame;
        [scrollView addSubview:controller.view];
    }
}

So you can see we are loading a new picViewController as long as it isn’t a page more then the the pics we have or less then 0. Then we add it to the scrollview at a set x/y.

So the idea is add page 0 first.. then add the next page to the width of page 0. Add page 2 the width of page 0 + width of page 1. That will give us the effect of scrolling back and forth

Now lets create our viewDidLoad method

- (void)viewDidLoad {
	[super viewDidLoad];

	// hiding the navigation bar
	self.navigationController.navigationBarHidden = YES;

    // view controllers are created lazily
    // in the meantime, load the array with placeholders which will be replaced on demand
    NSMutableArray *controllers = [[NSMutableArray alloc] init];
    for (unsigned i = 0; i < [self.jsonArray count]; i++) {
        [controllers addObject:[NSNull null]];
    }
    self.viewControllers = controllers;
    [controllers release];

    // a page is the width of the scroll view
    scrollView.pagingEnabled = YES;
    scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * [self.jsonArray count], scrollView.frame.size.height);
    scrollView.showsHorizontalScrollIndicator = NO;
    scrollView.showsVerticalScrollIndicator = NO;
    scrollView.scrollsToTop = NO;
    scrollView.delegate = self;

    [self loadPage:0];
    [self loadPage:1];
}

So you can see up top I am hiding the navigation bar and loading a array full of nothing. There is a good reason for this. If you have a user that has say 150 images, you don’t want to load them all at once. It will kill the app. You want to load things as they are about to be shown. So at the botom we are loading the first page and the next page. This is so we don’t want the user to wait to see the image. We want it shown right away.

Now that is down we need our final method for the class. So now lets create a scrollViewDidScroll method. This is part of the UIScrollViewDelegate. This is how we can tell our application a user has scrolled. In our case it will let us know when a user has flicked a finger to the left or right and load the next set of views.

- (void)scrollViewDidScroll:(UIScrollView *)sender {
    // Switch the indicator when more than 50% of the previous/next page is visible
    CGFloat pageWidth = scrollView.frame.size.width;
    int page = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;

    // load the visible page and the page on either side of it (to avoid flashes when the user starts scrolling)
    [self loadPage:page - 1];
    [self loadPage:page];
    [self loadPage:page + 1];
}

So based on the current width we can tell what page we are on in the scrollview. Then based on that page load the previous page, current page and next page. Don’t worry if you are on page 0, our loadPage method won’t be called for a -1 page.

That is all there is to it. I wrote this over 2-3 days so things might be missing. I am sorry for that. You can grab the code here and read through it.

Add a Wiggle

This will be a continuation of Multiple Objects and UITouch

If you do a long touch in the main section of the iPhone on a icon you will notice the icons will wiggle. This shows the user they can be deleted or moved around. So lets try to mimic that as best we can and when we stop a drag of our image it will wiggle for two seconds.

The first thing we need to do is import a framework to our project. So right click on the Frameworks tab. Then click on Add Framework. Then click QuartzCore FramwWork.

So first open up touchViewController.h and we need to add in two function

-(void)doWiggle:(UIView *)touchView;
-(void)endWiggle:(NSTimer*)timer;

Now open up touchViewController.m and we need to add in two functions

-(void)doWiggle:(UIView *)touchView {

	// grabbing the layer of the tocuhed view.
	CALayer *touchedLayer = [touchView layer];

	// here is an example wiggle
	CABasicAnimation *wiggle = [CABasicAnimation animationWithKeyPath:@"transform"];
	wiggle.duration = 0.1;
	wiggle.repeatCount = 1e100f;
	wiggle.autoreverses = YES;
	wiggle.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(touchedLayer.transform,0.1, 0.0 ,1.0 ,2.0)];

	// doing the wiggle
	[touchedLayer addAnimation:wiggle forKey:@"wiggle"];

	// setting a timer to remove the layer
	NSTimer *wiggleTimer = [NSTimer scheduledTimerWithTimeInterval:(2) target:self selector:@selector(endWiggle:) userInfo:touchedLayer repeats:NO];

}

-(void)endWiggle:(NSTimer*)timer {
	// stopping the wiggle now
	[((CALayer*)timer.userInfo) removeAllAnimations];
}

Now that we have two functions. We need add the touchesEnded method that gets called when you lift your finger off the screen.

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

	UITouch *touch = [[event allTouches] anyObject];

	if ([touch view] == image) {
		[self doWiggle:[touch view]];

	}
	else if ([touch view] == image2) {
		[self doWiggle:[touch view]];
	}
}

Now your image will wiggle when you end your drag.

You can grab the source code here.

Multiple Objects and UITouch

Here is a bit of a continuation of Working with UITouch

Please follow that tutorial first. We will be using the source code from that as our starting point. So once you have the source code in Xcode open up touchViewController.h and we need to add in one new outlet for our second image.

So we want the file to look like this. I have commented the line I have added in.

#import <UIKit/UIKit.h>
@interface touchViewController : UIViewController {
	IBOutlet UIImageView *image;
	IBOutlet UIImageView *image2; // added this
}

@end

Now we need to open up IB and create the new object and make the connection. Double click on touchViewController.xib. So my view now looks like this

My connections now look like this

Also we need to set something for the views in order for a click and drag to work on them. We need to make sure user interaction is enabled on the two UIImageViews we have in our main view. So the property for one looks like the following. Make sure you set this for both image views

Now you can save your view in IB and load up touchViewController.m. In our previous example we had the following method.

-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
	// get touch event
	UITouch *touch = [[event allTouches] anyObject];

	// get the touch location
	CGPoint touchLocation = [touch locationInView:touch.view];

	// move the image view
	image.center = touchLocation;
}

You can now either comment out that function or delete it. We don’t want to use it anymore. Instead add in the following method.

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
	// get touch event
	UITouch *touch = [[event allTouches] anyObject];
	CGPoint touchLocation = [touch locationInView:self.view];

	if ([touch view] == image) {
		// move the image view
		image.center = touchLocation;
	}
	else if ([touch view] == image2) {
		// move the image view
		image2.center = touchLocation;
	}
}

That function will now allow you to click on a imageview and hold the click and drag the image around the screen.

As always you can grab the source code here.

Access the Address Book

I got a request on how do I access the address book in the iPhone. So I took around 30 minutes to learn how and here is how we will do it. 

First create a new View-Based Application and call it addressBook.

Now the first thing we want to do is setup the 4 IBOutlets and the 1 IBAction for our project. We also need to include the headers for the address book framework. So open up addressBookViewController.h and you want to make it look like the following.

#import <UIKit/UIKit.h>
#import <AddressBook/AddressBook.h>
#import <AddressBookUI/AddressBookUI.h>
@interface addressBookViewController : UIViewController  {
	IBOutlet UIButton *button;
	IBOutlet UILabel *firstName;
	IBOutlet UILabel *lastName;
	IBOutlet UILabel *number;
}

-(IBAction)getContact;

@end

Now that they are added we need to add in the framework files. So you will right click on the addressBook in the Targets option on the left panel and click on Get Info.

At the bottom there is a + and click that and you need to add the following items in AddressBook.framework and AddressBookUI.framework

Now lets layout our view. So double click on addressBookViewController.xib. You want your view to look like the following.

I used the —–’s just so you can see there is a UILabel there. Now we need to setup the connections for the 4 outlets and the one action. You want it to look like the following

So now lets open up addressBookViewController.m and we want to add in the following methods.

-(IBAction)getContact {
	// creating the picker
	ABPeoplePickerNavigationController *picker = [[ABPeoplePickerNavigationController alloc] init];
	// place the delegate of the picker to the controll
	picker.peoplePickerDelegate = self;

	// showing the picker
	[self presentModalViewController:picker animated:YES];
	// releasing
	[picker release];
}

- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker {
    // assigning control back to the main controller
	[self dismissModalViewControllerAnimated:YES];
}

- (BOOL)peoplePickerNavigationController: (ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person {

	// setting the first name
    firstName.text = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);

	// setting the last name
    lastName.text = (NSString *)ABRecordCopyValue(person, kABPersonLastNameProperty);	

	// setting the number
	/*
	 this function will set the first number it finds

	 if you do not set a number for a contact it will probably
	 crash
	 */
	ABMultiValueRef multi = ABRecordCopyValue(person, kABPersonPhoneProperty);
	number.text = (NSString*)ABMultiValueCopyValueAtIndex(multi, 0);

	// remove the controller
    [self dismissModalViewControllerAnimated:YES];

    return NO;
}

- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier{
    return NO;
}

Now we can click build and go and see the following


As always you can grab the source here.

Working with UITouch

So here is a quick post on how to work with a UITouch. This tutorial will put a UIImageView on a view and then when you touch around the image view will be placed where you touch.

So lets start out by creating a new project and and use a View Based application as shown below.

Now I called the project touch.

So lets create an outlet for the image view we will create. So open up touchViewController.h. Add your outlet.

IBOutlet UIImageView *image;

Now take a image and drag it into your project. For this I will use the following image.

Now double click on touchViewController.xib and it will load IB. Take a UIImageView and drag it on the view. You also want to set the image to the image you dragged to your project. Your view should now look something like this

Now create the link from the outlet you created to the view so it looks like this in the File’s Owner.

Now save the file and quit. If you clicked build and go now we will just see a box on the screen and it does nothing. So we need to add in a function to handle the touch.

So open up touchViewController.m and add in the following code.

-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
	// get touch event
	UITouch *touch = [[event allTouches] anyObject];

	// get the touch location
	CGPoint touchLocation = [touch locationInView:touch.view];

	// move the image view
	image.center = touchLocation;
}

So pretty much we are grabbing a touch even on any object it may touch. Then we are getting the location of the touch. We will then take that and move the image.

The thing that sent me for a loop is the locationInView accepts a UIView so I was wondering why self.view didn’t work. I still have no clue but touch includes the view that was touched and that seems to work nicely.

The Apple code works off sub-classing your view which I have never done but this seems to work nicely.

As always here is the source code.

Next Page →