Categories
iOS & ObjectiveC Iphone Developer Technological

Creating a simple UICollectionView in iOS

Steps

1) Create Master-Detail Application & Replace the MasterViewController

First we want to create a Master-Detail Application just because it sets up a Master-Detail relationship even though thats the first thing we are going to break :).  So go ahead and create a new project in XCode4 based on a Master-Detail Application type.  Use ARC, Storyboards and CoreData because we will use CoreData to store information.  Your storyboard should look like this:

Master-Detail Storyboard
Master-Detail Storyboard

Now select the Master scene until its highlighted in blue and delete it with the Delete key.  We simply replace it by dragging in a UICollectionViewController onto the storyboard in its place.  This places a UICollectionViewController scene with a small empty collectionview cell in the top left corner.  I made a few adjustments to mine and here is what it looks like but Ill go over those later:

UICollectionViewController Storyboard
UICollectionViewController Storyboard

The changes I made to it are the following:

a – Select the entire scene (again until its highlighted blue) and change its Class type from UICollectionViewController to MasterViewController.

b – Enlarged the UICollectionViewCell from 50×50 to 145×145 in the Dimension’s Inspector

Here are some clips of the Identity Inspector of the MasterViewController and Dimension’s Inspector of the UICollectionViewCell:

UICollectionViewController Identity Inspector
UICollectionViewController Identity Inspector
UICollectionViewCell Dimensions Inspector
UICollectionViewCell Dimensions Inspector

We did set the new UICollectionViewController to a new class, the MasterViewController class.  We must do the same with the UICollectionViewCell but we must create its class first.

2)  Modify the MasterViewController class with the following in the .m file:

#import “MasterViewController.h”

#import “DetailViewController.h”

#import “MyCustomCell.h”

#import “AppDelegate.h”

static NSString *CellIdentifier = @”MyCustomCell”;

@interface MasterViewController ()

{

NSMutableArray *_objectChanges;

NSMutableArray *_sectionChanges;

}

@end

Now let’s go through the methods.  First the UICollectionView methods, which are quite similar to the UITableViewController methods:

#pragma mark – UICollectionView

– (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section

{

id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];

return [sectionInfo numberOfObjects];

}

// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:

– (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath

{

MyCustomCell *cell = (MyCustomCell *)[collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];

NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];

[cell setImage:[UIImage imageWithData:[object valueForKey:@”photoImageData”]]];

return cell;

}

– (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

{

if ([[segue identifier] isEqualToString:@”showDetail”]) {

NSIndexPath *indexPath = [[self.collectionView indexPathsForSelectedItems] lastObject];

NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];

[[segue destinationViewController] setDetailItem:object];

}

}

The MyCustomCell Class is the one we will create in the next section.  For now we are simply filling in the cell’s image data with some fetched managed object which we will also create later.  A couple of more interesting tidbits for example; we gave our UICollectionViewCell a reuse identifier and we gave our segue an identifier as well.  We must make sure these identifiers also exist in the storyboard inspectors for the cell and segue respectively.

3) Create UICollectionViewCell Class & Connect it

Let’s go ahead and Create a New File in our project, base it off of Objective C Class.  Type in UICollectionViewCell as the subclass and name it MyCustomCell.  We are simply going to define a class for our UICollectionViewCell and once we are finished, we must go to Storyboard and set our cell to use this new class type.

Add a UIImageView property to the class so that your .h file looks like this:

#import <UIKit/UIKit.h>

@interface MyCustomCell : UICollectionViewCell{

IBOutlet UIImageView *imageView;

}

-(void)setImage:(UIImage *)image;

@end

and now implement the setter in your .m so that it looks like this:

#import “MyCustomCell.h”

@implementation MyCustomCell

-(void)setImage:(UIImage *)image{

[imageView setImage:image];

}

@end

4) Create the data model.  First let’s do the easiest part, which is creating the data model.  Select your project’s xcdatamodel file and create a new Entity, call it Snapshots if you’d like.  Now add 3 attributes to it and make them of this type:

Core Data Entity Data Model
Core Data Entity Data Model

Once that is done, we have a storage container for our data.  Let’s look at the code used by our app to access this store and manipulate it.

#pragma mark – Fetched results controller

– (NSFetchedResultsController *)fetchedResultsController

{

if (_fetchedResultsController != nil) {

return _fetchedResultsController;

}

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

// Edit the entity name as appropriate.

NSEntityDescription *entity = [NSEntityDescription entityForName:@”Snapshots” inManagedObjectContext:self.managedObjectContext];

[fetchRequest setEntity:entity];

// Set the batch size to a suitable number.

[fetchRequest setFetchBatchSize:20];

// Edit the sort key as appropriate.

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@”photoName” ascending:NO];

NSArray *sortDescriptors = @[sortDescriptor];

[fetchRequest setSortDescriptors:sortDescriptors];

// Edit the section name key path and cache name if appropriate.

// nil for section name key path means “no sections”.

NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@”Master”];

aFetchedResultsController.delegate = self;

self.fetchedResultsController = aFetchedResultsController;

NSError *error = nil;

if (![self.fetchedResultsController performFetch:&error]) {

// Replace this implementation with code to handle the error appropriately.

// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.

NSLog(@”Unresolved error %@, %@”, error, [error userInfo]);

abort();

}

return _fetchedResultsController;

}

– (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo

atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type

{

NSMutableDictionary *change = [NSMutableDictionary new];

switch(type) {

case NSFetchedResultsChangeInsert:

change[@(type)] = @(sectionIndex);

break;

case NSFetchedResultsChangeDelete:

change[@(type)] = @(sectionIndex);

break;

}

[_sectionChanges addObject:change];

}

– (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject

atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type

newIndexPath:(NSIndexPath *)newIndexPath

{

NSMutableDictionary *change = [NSMutableDictionary new];

switch(type)

{

case NSFetchedResultsChangeInsert:

change[@(type)] = newIndexPath;

break;

case NSFetchedResultsChangeDelete:

change[@(type)] = indexPath;

break;

case NSFetchedResultsChangeUpdate:

change[@(type)] = indexPath;

break;

case NSFetchedResultsChangeMove:

change[@(type)] = @[indexPath, newIndexPath];

break;

}

[_objectChanges addObject:change];

}

– (void)controllerDidChangeContent:(NSFetchedResultsController *)controller

{

if ([_sectionChanges count] > 0)

{

[self.collectionView performBatchUpdates:^{

for (NSDictionary *change in _sectionChanges)

{

[change enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, id obj, BOOL *stop) {

NSFetchedResultsChangeType type = [key unsignedIntegerValue];

switch (type)

{

case NSFetchedResultsChangeInsert:

[self.collectionView insertSections:[NSIndexSet indexSetWithIndex:[obj unsignedIntegerValue]]];

break;

case NSFetchedResultsChangeDelete:

[self.collectionView deleteSections:[NSIndexSet indexSetWithIndex:[obj unsignedIntegerValue]]];

break;

case NSFetchedResultsChangeUpdate:

[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:[obj unsignedIntegerValue]]];

break;

}

}];

}

} completion:nil];

}

if ([_objectChanges count] > 0 && [_sectionChanges count] == 0)

{

[self.collectionView performBatchUpdates:^{

for (NSDictionary *change in _objectChanges)

{

[change enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, id obj, BOOL *stop) {

NSFetchedResultsChangeType type = [key unsignedIntegerValue];

switch (type)

{

case NSFetchedResultsChangeInsert:

[self.collectionView insertItemsAtIndexPaths:@[obj]];

break;

case NSFetchedResultsChangeDelete:

[self.collectionView deleteItemsAtIndexPaths:@[obj]];

break;

case NSFetchedResultsChangeUpdate:

[self.collectionView reloadItemsAtIndexPaths:@[obj]];

break;

case NSFetchedResultsChangeMove:

[self.collectionView moveItemAtIndexPath:obj[0] toIndexPath:obj[1]];

break;

}

}];

}

} completion:nil];

}

[_sectionChanges removeAllObjects];

[_objectChanges removeAllObjects];

}

I know its long, but its pretty simple.  The first method, – (NSFetchedResultsController *)fetchedResultsController, basically opens up the store, fetches all entities by the name “Snapshots” and places them into a special object called NSFetchedResultsController.

The didChangeSection method is called when there is a change within a section.  We only have 1 section in our UICollectionView.

The didChangeObject method is called when a particular object is changed within our UICollectionView.

The controllerDidChangeContent actually manages the changes made.  Basically we update our two arrays, _sectionChanges and _objectChanges with each change in the data in order to keep our UICollectionView current.

5) Connect to Flickr API.  So what constitutes a change in those sections and objects?  There is an acronym that datastore managers use, CRUD, which basically says that everytime you create, read, update or delete you create a transaction.  Thats basically what we want to track (except for the read part :)).  So whenever we download a new photo to our datastore, update a photo or delete one, we trigger changes in objects and thus in sections.

We want to use the Flickr API to get images from the web and populate our collection view.  We are basically going to perform a fetch to Flickr API using our own key or identifier.  You must register for one at flickr.com/.

a – Get FlickrAPIKey and add it here to this string constant atop your .m file like so:

NSString *const FlickrAPIKey = @"YOURAPIKEYVALUE";

b – Add the loadFlickrPhotos method to fetch pics from the web.  So add this method:

– (void)loadFlickrPhotos{

// 1. Build your Flickr API request w/Flickr API key in FlickrAPIKey.h

NSString *urlString = [NSString stringWithFormat:@”http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=%@&tags=%@&per_page=10&format=json&nojsoncallback=1″, FlickrAPIKey, @”bayern”];

NSURL *url = [NSURL URLWithString:urlString];

// 2. Get URLResponse string & parse JSON to Foundation objects.

NSString *jsonString = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];

NSError *e = nil;

NSDictionary *results = [NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding]options:NSJSONReadingMutableContainers error:&e];

photos = [[results objectForKey:@”photos”] objectForKey:@”photo”];

for (NSDictionary *photo in photos) {

// 3.a Get title for e/ photo

NSString *title = [photo objectForKey:@”title”];

[photoNames addObject:(title.length > 0 ? title : @”Untitled”)];

// 3.b Construct URL for e/ photo.

NSString *photoURLString = [NSString stringWithFormat:@”http://farm%@.static.flickr.com/%@/%@_%@_s.jpg”, [photo objectForKey:@”farm”], [photo objectForKey:@”server”], [photo objectForKey:@”id”], [photo objectForKey:@”secret”]];

[photoURLs addObject:[NSURL URLWithString:photoURLString]];

}

// Process into CoreData

[self processCoreData];

}

It basically looks for Flickr photos of Bayern and stores the results in the photos ivar.  Now we must populate our CoreData db with these data.

c – Populate our CoreData model.  Add the processCoreData method to your MasterViewController.m like so:

-(void)processCoreData{

AppDelegate *myDelegate = [[UIApplication sharedApplication] delegate];

for (NSDictionary *photoDictionary in photos){

NSManagedObject *photoModel = [NSEntityDescription insertNewObjectForEntityForName:@”AFPhotoModel” inManagedObjectContext:myDelegate.managedObjectContext];

//[photoModel setValue:[photoDictionary valueForKey:@”rating”] forKey:@”photoRating”];

[photoModel setValue:[photoDictionary valueForKey:@”title”] forKey:@”photoName”];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{

//Build URL

NSString *photoURLString = [NSString stringWithFormat:@”http://farm%@.static.flickr.com/%@/%@_%@_s.jpg”, [photoDictionary objectForKey:@”farm”], [photoDictionary objectForKey:@”server”], [photoDictionary objectForKey:@”id”], [photoDictionary objectForKey:@”secret”]];

NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:photoURLString]];

dispatch_async(dispatch_get_main_queue(), ^{

[photoModel setValue:imageData forKey:@”photoImageData”];

});

});

}

}

Voila!  Run your app!

Categories
iOS & ObjectiveC Iphone Developer Technological

iOS7 – UIKit Dynamics

iOS7 Series – UIKit Dynamics

 

Incorporating UIKitDynamics into your app is pretty simple!  The great thing about it is that you really get the most bang for your buck because the end result has a really big WOW Factor which is certain to impress your users.  Let’s take a quick conceptual drive around UIKitDynamics.

First we adopt the protocol into the ViewController which will implement UIKitDynamics, why?  Well because the objects which will be animated in the end will have to send back a lot of signals like “Hey, I collided with a boundary” or “hey I just hit somebody else and I was going this fast, in this direction”.  In order to receive these messages we use a delegate and its callbacks.

@interface … <UICollisionBehaviorDelegate>

We would need to create the view to animate and the property to reference a UIDynamicAnimator, which is the object in charge of handling animations in UIKitDynamics.

@property (nonatomic, weak) IBOutlet UIView *square1;

@property (nonatomic) UIDynamicAnimator* animator;

Basically we would prep all we need in viewDidLoad, such as instantiating an animator to call the shots inside a particular reference view.  Then we create a behavior or set of behaviors we wish to assign to our animatable view.  We define boundaries so we can keep our objects inside a view.  Finally we add the behaviors to the animator and set the viewcontroller as the delegate as well as set that animator object to our property in order to hold a reference to it.

That’s it!  Now we just sit back and get messages from the animator and the animated view via the callbacks.

– (void)viewDidLoad{

[super viewDidLoad];

UIDynamicAnimator* animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

UIGravityBehavior* gravityBeahvior = [[UIGravityBehavior alloc] initWithItems:@[self.square1]];

UICollisionBehavior* collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[self.square1]];

collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;

[animator addBehavior:gravityBeahvior];

[animator addBehavior:collisionBehavior];

collisionBehavior.collisionDelegate = self;

self.animator = animator;

}

-(void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier atPoint:(CGPoint)p{

// Lighten the background color when the view is in contact with a boundary.

[(UIView*)item setBackgroundColor:[UIColor lightGrayColor]];

}

-(void)collisionBehavior:(UICollisionBehavior *)behavior endedContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier{

// Restore the default color when ending a contcact.

[(UIView*)item setBackgroundColor:[UIColor grayColor]];

}

Ok so let’s use it in a simple example.  Let’s say we are building a Restaurant rating app.  It’s a single view app with a plain vanilla UIViewController which has these properties connected to those outlets:

@property (nonatomic, strong) IBOutlet UILabel *restaurantName;

@property (nonatomic, strong) IBOutlet UILabel *restaurantAddress;

//STAR RATING

@property (nonatomic, strong) IBOutlet UIImageView *stars1;

@property (nonatomic, strong) IBOutlet UIImageView *stars2;

@property (nonatomic, strong) IBOutlet UIImageView *stars3;

@property (nonatomic, strong) IBOutlet UIImageView *stars4;

@property (nonatomic, strong) IBOutlet UIImageView *stars5;

 

First let’s create the animator that will handle the animation inside our viewDidLoad:

// Create animator

UIDynamicAnimator* animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

Now we add the views we want to animate to the behaviors we want to implement:

//Create behaviors

    UIGravityBehavior* gravityBeahvior = [[UIGravityBehavior alloc] initWithItems:@[self.stars1,self.stars2, self.stars3, self.stars4,self.stars5]];

UICollisionBehavior* collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[self.stars1,self.stars2, self.stars3, self.stars4,self.stars5]];

UIDynamicItemBehavior* propertiesBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.stars1,self.stars2, self.stars3, self.stars4,self.stars5]];

propertiesBehavior.elasticity = 5;

Notice that the last behavior is actually created to modify certain physical properties of an object, in this case elasticity.  There are other properties we can modify in this manner.

We can also add specific boundaries but in many cases we will want to simply use the view’s edges as the natural boundaries, so we use this line:

    collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;

Finally we add the behaviors to the animator, set the delegate to self and reference our animator through its property:

[animator addBehavior:gravityBeahvior];

[animator addBehavior:collisionBehavior];

collisionBehavior.collisionDelegate = self;

self.animator = animator;

Finally just add the following delegate callbacks to decide what gets done when a collision occurs:

-(void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier atPoint:(CGPoint)p{

// Lighten the background color when the view is in contact with a boundary.

[(UIView*)item setBackgroundColor:[UIColor lightGrayColor]];

}

-(void)collisionBehavior:(UICollisionBehavior *)behavior endedContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier{

// Restore the default color when ending a contcact.

[(UIView*)item setBackgroundColor:[UIColor grayColor]];

}

That’s it!  There are lots of neat effects you can use but don’t overdo it or your users will sue you for giving them vertigo! J

Categories
iOS & ObjectiveC Iphone Developer Technological

How to create an app – Not programmatically

A fellow coder asked:

“How does one go about developing an app from scratch?  Where does one start?”

Blueprint iOS App Design by Marcio Valenzuela Santiapps.com
Blueprint iOS App Design

My response follows:

Some will say its a matter of style and you have to find your own. And eventually you will, after a lot of copy-paste programming. I started in 2009 and my first app was a copy-paste of a few different projects. All the app did was connect to the Internet, download XML and parse it into a uilabel.

Then I decided to look into more complex app samples. So I downloaded a few from Apple. In particular I remember iPhoneCoreDataRecipes. In hindsight, it was too complex for a beginner but I’m very visual. So the variety of viewcontroller objects made it easier for me to understand. I intentionally stayed away from the coredata logic and took it for granted. I focused on how data was created, passed around and manipulated in the program. I remember I printed out the main class files, about 10 classes in all. Then I started making drawings of each object and tried to decipher who called who and how. That’s when I learned about delegates and protocols. That’s also when I learned to use breakpoints because I went crazy trying to follow NSLogs. It was a great learning experience.

Then I tried following a Stanford course…it helped but it was confusing. So a funny thing happened…I bought Learn Cocos2d by Ray Wenderlich and Rod Strougo.
This is when OOP sank in! Oh yeah, everything is an object!

This was a real eye opener for me. Then I started using SO and the Dev forums which taught me to read the documentation. I learned because for the most part they would all say “Read the docs!!!”. So I was forced to learn how to read documentation. After that I was able to read other docs like Twitter and Cocos2d and many others.

So now when I start a project I grab a pencil and a lot of paper. I start with the business aspects, marketing namely; who is the app for, what others exist, how will they use it, what would the app need to have for those users to like it?

Then I draw viewcontroller objects. Kinda like a huge storyboard with viewcontrollers. This forces me to visually see where the user will start, where data will be created, manipulated and consumed. This way I know which view controller objects need access to data and how they will receive or send it.

Finally I start to write method names of what each view controller will do and how it will interact with other and the data it needs, right below each object in the pencil storyboard.

Only then will I start to code classes. And even when I DO code classes and their methods, I will usually create all empty methods with just comment lines of what a block of code should do. I’ve picked up some personal ideas along the way such as adding an Empty File to be my Journal of what I did each day and what to do the next time I open a project. I also use #warning #PENDING lines to call attention to the comment lines which say what to do but needs to be done in each class.

When in designing the pencil storyboard I’ll usually google for third party libraries that can do certain things that perhaps apple’s native ios sdk won’t do.

Looking at sample code helps a lot. It teaches you some basic patterns like how to use singletons to connect to resources or synchronize data stores and use blocks or protocols to communicate efficiently.

In the end you will develop your own style and procedures. I believe time is the answer you’re looking for. 🙂