View Images like Photos.app

October 8, 2008    JSON Programming

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 <UIScrollViewDelegate> {
    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.



comments powered by Disqus