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!

iOS Smarties :)

iOS & Objective C Smarties Code Snippets by Marcio Valenzuela Santiapps.com
iOS & Objective C Smarties Code Snippets

Everyone loves Smarties!  And much the same way Smarties Candies make you smarter… today we are talking about Code Snippets that make you….er Smarter!  More than a source of cut/paste Objective C source code, this is meant to be a quick reference.  As such, some of these will be incomplete and I will be filling them up as I go along. 

1)   UIAlertView

UIAlertView *internetAlert = [[UIAlertView alloc] initWithTitle:@”No hay farmacias”

message:@”Favor cambie sus parámetros”

delegate:self

cancelButtonTitle:@”Cancelar”

otherButtonTitles:@”Ok”, nil];

[internetAlert show];

2)   NSNotification

//1. Register as observer of notifications in viewDidLoad

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveTestNotification:) name:@”TestNotification” object:nil];

//2. NSNotifCtr CLEANUP in viewDidUnload

[[NSNotificationCenter defaultCenter] removeObserver:self];

//4. Post notif to NSNotif in calling method OTHER CLASS

[[NSNotificationCenter defaultCenter]

postNotificationName:@”TestNotification”

object:self];

//3. Method to prove notif received…

– (void) receiveTestNotification:(NSNotification *) notification

{

if ([[notification name] isEqualToString:@”TestNotification”])

NSLog (@”Successfully received the test notification!”);

}

3)   UIActivityIndicator – NSTimer – then completion blocks

//1. Set activity indicator

UIApplication *sharedApplication = [UIApplication sharedApplication];

sharedApplication.networkActivityIndicatorVisible = YES;

//INSERT NSTimer here J

//2. Hide activity indicator

UIApplication *sharedApplication = [UIApplication sharedApplication];

sharedApplication.networkActivityIndicatorVisible = NO;

works the same with an IBOutlet UIActivityIndicator

4)   UISearchBar

UISearchBarDelegate, UISearchDisplayDelegate

@property (strong,nonatomic) NSMutableArray *filteredResultsArray;

@property (strong,nonatomic) IBOutlet UISearchBar *resultsSearchBar;

//UISearch in INIT or LOAD method

// Initialize the filteredResults array with its count of Locations NSManagedObjects

self.filteredResultsArray = [NSMutableArray arrayWithCapacity:[self.originalArray count]];

//ios6 refresh control ONLY

if ([UIRefreshControl class]) {

self.refreshControl = [[UIRefreshControl alloc] init];

[self.refreshControl addTarget:self action:@selector(reload) forControlEvents:UIControlEventValueChanged];

[self reload];

[self.refreshControl beginRefreshing];

} else {

//do nothing

}

//nORIS

if (tableView == self.searchDisplayController.searchResultsTableView) {

return [filteredResultsArray count];

} else {

return [self.originalArray count];

}

//cFRAIP

if (tableView == self.searchDisplayController.searchResultsTableView) {

// IF ITS A SEARCH TABLE…>>>>>>>>>>

//Use filteredResultsArray data

} else {

//Otherwise use self.originalArray

}

5)   Reminders & Events

/////////EVENTS

// SPECIAL CALENDAR OBJECT CLASS

#import <EventKit/EventKit.h>

+(EKEventStore*)eventStore;

+(EKCalendar*)calendar;

+(EKCalendar*)createAppCalendar;

// WHAT IT DOES

static EKEventStore* eStore = NULL;

+(EKEventStore*)eventStore{

//keep a static instance of eventStore

if (!eStore) {

eStore = [[EKEventStore alloc] init];

}

return eStore;

}

+(EKCalendar*)createAppCalendar

{

EKEventStore *store = [self eventStore];

//1 fetch the local event store source

EKSource* localSource = nil;

for (EKSource* src in store.sources) {

if (src.sourceType == EKSourceTypeCalDAV) {

localSource = src;

}

if (src.sourceType == EKSourceTypeLocal && localSource==nil) {

localSource = src;

}

}

if (!localSource) return nil;

//2 CREATE A NEW CALEDNAR FOR ITEMS!!!!!!!!!!!!!!!

EKCalendar* newCalendar = [EKCalendar calendarWithEventStore: store];

newCalendar.title = kAppCalendarTitle;

newCalendar.source = localSource;

newCalendar.CGColor = [[UIColor colorWithRed:0.8 green:0.2 blue:0.6 alpha:1] CGColor];

//3 save the calendar in the event store

NSError* error = nil;

[store saveCalendar: newCalendar commit:YES error:&error];

if (!error) {

return nil;

}

//4 store the calendar id

NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];

[prefs setValue:newCalendar.calendarIdentifier forKey:@”appCalendar”];

[prefs synchronize]; return newCalendar;

}

+(EKCalendar*)calendar{

//1

EKCalendar* result = nil;

EKEventStore *store = [self eventStore];

//2 check for a persisted calendar id

NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];

NSString *calendarId = [prefs stringForKey:@”appCalendar”];

//3

if (calendarId && (result = [store calendarWithIdentifier: calendarId])) {

return result;

}

//4 check if calendar name exists

for (EKCalendar* cal in store.calendars) {

if ([cal.title compare: kAppCalendarTitle]==NSOrderedSame) {

if (cal.immutable == NO) {

[prefs setValue:cal.calendarIdentifier

forKey:@”appCalendar”]; [prefs synchronize]; return cal;

}

}

}

//5 if no calendar is found whatsoever, create one

result = [self createAppCalendar];

//6

return result;

}

// ADD ENTRY FROM VC THRU CALEDNAR OBJECT

-(void)addItem:(NSDictionary*)item toCalendar:(EKCalendar*)calendar{

EKEvent* event = [EKEvent eventWithEventStore:[AppCalendar eventStore]];

event.calendar = calendar;

EKAlarm* myAlarm = [EKAlarm alarmWithRelativeOffset: – 06*60 ];

[event addAlarm: myAlarm];

NSDateFormatter* frm = [[NSDateFormatter alloc] init];

[frm setDateFormat:@”MM/dd/yyyy HH:mm zzz”];

[frm setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@”en_US”]];

event.startDate = [frm dateFromString:[show objectForKey:@”startDate”]];

event.endDate = [frm dateFromString:[show objectForKey:@”endDate”]];

event.title = [item objectForKey:@”title”];

event.URL = [NSURL URLWithString:[item objectForKey:@”url”]];

event.location = @”My Office”;

event.notes = [item objectForKey:@”tip”];

//add recurrence

NSNumber* weekDay = [item objectForKey:@”dayOfTheWeek”];

EKRecurrenceDayOfWeek* itemDay = [EKRecurrenceDayOfWeek dayOfWeek: [weekDay intValue]];

EKRecurrenceEnd* runFor3Months = [EKRecurrenceEnd recurrenceEndWithOccurrenceCount:12];

EKRecurrenceRule* myReccurrence = [[EKRecurrenceRule alloc] initRecurrenceWithFrequency:EKRecurrenceFrequencyWeekly interval:1

daysOfTheWeek:[NSArray arrayWithObject:itemDay] daysOfTheMonth:nil monthsOfTheYear:nil weeksOfTheYear:nil daysOfTheYear:nil setPositions:nil end:runFor3Months];

[event addRecurrenceRule: myReccurrence];

//1 save the event to the calendar

NSError* error = nil;

[[AppCalendar eventStore] saveEvent:event span:EKSpanFutureEvents commit:YES error:&error];

//2 show the edit event dialogue

EKEventEditViewController* editEvent = [[EKEventEditViewController alloc] init];

editEvent.eventStore = [AppCalendar eventStore]; editEvent.event = event;

editEvent.editViewDelegate = self;

[self presentViewController:editEvent animated:YES completion:^{

UINavigationItem* item = [editEvent.navigationBar.items objectAtIndex:0]; item.leftBarButtonItem = nil;

}];

}

//////REMINDERS

//EVENT CLASS OBJECT

– (id) init {

self = [super init];

if (self) {

_eventStore = [[EKEventStore alloc] init];

[_eventStore requestAccessToEntityType:EKEntityTypeEvent

completion:^(BOOL granted, NSError *error) {

if (granted) {

_eventAccess = YES;

} else {

NSLog(@”Event access not granted: %@”, error);

}

}];

[_eventStore requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError *error) {

if (granted) { _reminderAccess = YES;

} else {

NSLog(@”Reminder access not granted: %@”, error);

}

}];

//Initialize the dispatch queue

_fetchQueue = dispatch_queue_create(“com.santiapps.fetchQueue”, DISPATCH_QUEUE_SERIAL);

}

return self;

}

//LOOP THRU CALENDARS

NSArray * allCalendars = [_eventStore calendarsForEntityType:EKEntityMaskEvent |

EKEntityMaskReminder];
NSMutableArray * writableCalendars = [NSMutableArray array]; for (EKCalendar * calendar in allCalendars) {

if (calendar.allowsContentModifications) { [writableCalendars addObject:calendar];

} }

EKCalendar *calendar = eventStore.defaultCalendarForNewEvents;

//CREATE EVENT

//1

EKEvent *event = [EKEvent eventWithEventStore:eventStore]; event.title = @”Hello world!”;
event.startDate = startDate;
event.endDate = endDate;

EKAlarm *alarm = [EKAlarm alarmWithRelativeOffset:-1800]; [event addAlarm:alarm];

event.calendar = eventStore.defaultCalendarForNewEvents;

//2

NSError *err;
BOOL saved = [eventStore saveEvent:event span:EKSpanThisEvent commit:YES error:&err];

if (!saved) {
NSLog(@”Error creating the event”);

}

//REMINDER

//1

EKReminder *reminder = [EKReminder reminderWithEventStore:eventStore];

reminder.title = @”Creating my first reminder”; reminder.calendar = [eventStore defaultCalendarForNewReminders];

NSCalendar *calendar =
[NSCalendar currentCalendar];

NSUInteger unitFlags = NSEraCalendarUnit |

NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;

NSDateComponents *dueDateComponents = [calendar components:unitFlags fromDate:dueDate];

//1.5. Set the due date

reminder.dueDateComponents = dueDateComponents;

//2

NSError *err;

BOOL success =

[eventStore saveReminder:reminder commit:YES error:&err];

if (!success) {
NSLog(@”Error creating reminder”);

}

6)   Local Notifications

//test notif

NSDate *date = [NSDate dateWithTimeIntervalSinceNow:10];

UILocalNotification *localNotification = [[UILocalNotification alloc] init];

localNotification.fireDate = date;

localNotification.timeZone = [NSTimeZone defaultTimeZone];

localNotification.alertBody = @”I am a local notification!”;

localNotification.soundName = UILocalNotificationDefaultSoundName;

[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];

7)   Sort NSArrays for tableviews

-(void)sort{

NSSortDescriptor *sortDescriptor;

sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@”distance” ascending:YES] autorelease];

NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];

NSArray *sortedArray;

sortedArray = [self.annotationsToSort sortedArrayUsingDescriptors:sortDescriptors];

self.annotationsToSort = [(NSArray*)sortedArray mutableCopy];

[self.tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.5];

}

8)   Contacts

// Read Contact list xml formatted (Array of Dictionaries)

//read directory here

dispatch_async(kBgQueue , ^{

contacts = [NSArray arrayWithContentsOfURL: kContactsList];

dispatch_async(dispatch_get_main_queue(), ^{

[self.tableView reloadData]; });

});

// Do this

//NO ADD THEM TO AB

dispatch_async(kBgQueue , ^{

NSString* request = [NSString stringWithFormat:@”%@?%@”, kContactsList, ids]; //1

NSData *responseData = [NSData dataWithContentsOfURL:[NSURL URLWithString:request]]; //2

//parse vCard data

ABAddressBookRef addressBook = ABAddressBookCreate(); //1

ABRecordRef record = ABPersonCreate(); //2

NSArray* importedPeople = (__bridge_transfer NSArray*)

ABPersonCreatePeopleInSourceWithVCardRepresentation( record, (__bridge CFDataRef)responseData); //3

CFBridgingRelease(record); //4

//define few constants

__block NSMutableString* message = [NSMutableString stringWithString:

@”Contacts imported AB. Add them in Facebook: “];

NSString* serviceKey = (NSString*)kABPersonSocialProfileServiceKey;

NSString* facebookValue = (NSString*)

kABPersonSocialProfileServiceFacebook;

NSString* usernameKey = (NSString*)kABPersonSocialProfileUsernameKey;

//loop over people and get their facebook

for (int i=0;i<[importedPeople count];i++) { //1

ABRecordRef personRef = (__bridge ABRecordRef) [importedPeople objectAtIndex:i];

ABAddressBookAddRecord(addressBook, personRef, nil);

//2

ABMultiValueRef profilesRef = ABRecordCopyValue( personRef, kABPersonSocialProfileProperty);

NSArray* profiles = (__bridge_transfer NSArray*) ABMultiValueCopyArrayOfAllValues(profilesRef);

//3

for (NSDictionary* profile in profiles) { //4

NSString* curServiceValue = [profile objectForKey:serviceKey]; //5

if ([facebookValue compare: curServiceValue] == NSOrderedSame) { //6

[message appendFormat: @”%@, “, [profile objectForKey: usernameKey]];

}

}

//7

CFBridgingRelease(profilesRef);

}

//save to addressbook

ABAddressBookSave(addressBook, nil);

CFBridgingRelease(addressBook);

//show done alert

dispatch_async(dispatch_get_main_queue(), ^{ [[[UIAlertView alloc]

initWithTitle: @”Done!” message: message

delegate: nil cancelButtonTitle:@”OK” otherButtonTitles:nil] show];

});

});

9)   Mapkit

//CHECK LOCATION SERVICES

-(void)checkLocationServices{

BOOL locationAllowed = [CLLocationManager locationServicesEnabled];

if (locationAllowed==NO) {

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@”Location Service Disabled”

message:@”Re-enable in Settings for this app.”

delegate:nil

cancelButtonTitle:@”OK”

otherButtonTitles:nil];

[alert show];

[alert release];

}

}

//GET LOCATION

-(void)getLocation{

locationManager = [[CLLocationManager alloc] init];

locationManager.delegate = self;

locationManager.distanceFilter = 10;

locationManager.desiredAccuracy = kCLLocationAccuracyBest;

[locationManager startUpdatingLocation];

//- (void)startMonitoringSignificantLocationChanges

_mapView.showsUserLocation = YES;

}

-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {

self.userLocation = [locations lastObject];

NSLog(@”userLocation is %f, %f”, self.userLocation.coordinate.latitude, self.userLocation.coordinate.longitude);

CLLocationCoordinate2D zoomLocation;

zoomLocation.latitude = self.userLocation.coordinate.latitude;

zoomLocation.longitude = self.userLocation.coordinate.longitude;

NSLog(@”Using %g,%g”, zoomLocation.latitude, zoomLocation.longitude);

//StopUpdating Location so it wont change all the time

[locationManager stopUpdatingLocation];

}

//GET ANNOTATIONS

CLLocation *pinLocation = [[CLLocation alloc] initWithLatitude:annotation.coordinate.latitude longitude:annotation.coordinate.longitude];

CLLocationDistance calculatedDistance = [pinLocation distanceFromLocation:self.userLocation];

annotation.distance = calculatedDistance/1000;

//PLOT ANNOTATIONS

-(void)plotAnnotations{

//Clean out old annotations

for (id<MKAnnotation> annotation in _mapView.annotations) {

[_mapView removeAnnotation:annotation];

}

//THIS LOGS THE LOCATIONSTOSORT ARRAY OF FVC

//NSLog(@”self.myLocationsToSort %@”, self.myLocationsToSort);

for (MyLocation *annotation in self.myLocationsToSort) {

//Add annotation to mapview

[_mapView addAnnotation:annotation];

}

}

– (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {

static NSString *identifier = @”MyLocation”;

if ([annotation isKindOfClass:[MyLocation class]]) {

//test if mapviewnil

if (_mapView == nil) {

NSLog(@”NIL”);

}

MKPinAnnotationView *annotationView = (MKPinAnnotationView *) [_mapView dequeueReusableAnnotationViewWithIdentifier:identifier];

if (annotationView == nil) {

annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];

annotationView.enabled = YES;

annotationView.canShowCallout = YES;

annotationView.image=[UIImage imageNamed:@”arrest.png”];

} else {

annotationView.annotation = annotation;

}

//instatiate a detail-disclosure button and set it to appear on right side of annotation

UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];

//[infoButton addTarget:self action:@selector(showDetailView:annotationView.annotation) forControlEvents:UIControlEventTouchUpInside];

annotationView.rightCalloutAccessoryView = infoButton;

return annotationView;

}

return nil;

}

//GET ANNOTATIONS

//ZOOM INTO LOCATION

– (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation {

// We have location, do your logic of setting the map region here.

CLLocationCoordinate2D zoomLocation;

NSLog(@”location in FirstVC %@”, self.userLocation);

//Test if userLocation has a value

NSLog(@”NOCITYSELECTED vDL FVC”);

zoomLocation.latitude = self.userLocation.coordinate.latitude;

zoomLocation.longitude = self.userLocation.coordinate.longitude;

CLLocationDistance visibleDistance = 5000; // 5 kilometers

MKCoordinateRegion adjustedRegion = MKCoordinateRegionMakeWithDistance(zoomLocation, visibleDistance, visibleDistance);

[_mapView setRegion:adjustedRegion animated:YES];

}

//TAP ON PINS – ANNOTATIONS

//Called when disclosure tapped

– (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control{

if (![view.annotation isKindOfClass:[MyLocation class]])

return;

// use the annotation view as the sender

[self performSegueWithIdentifier:@”DetailVC” sender:view];

}

10)                  UICustomization/Appearance

[[UINavigationBar appearance] setShadowImage:[[UIImage alloc] init]];

[self.tabBarItem setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIFont fontWithName:@”Futura” size:10.0f], UITextAttributeFont, nil] forState:UIControlStateNormal];

UIImage *gradientImage32 = [[UIImage imageNamed:@”FirstCellLogo.png”] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];

[[UINavigationBar appearance] setBackgroundImage:gradientImage32 forBarMetrics:UIBarMetricsDefault];

[self.tabBarItem setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIFont fontWithName:@”Futura” size:10.0f], UITextAttributeFont, nil] forState:UIControlStateNormal];

[[UITabBar appearance] setSelectedImageTintColor:[UIColor redColor]];

11)                  LetterPress Effect

//letterpress effect the label

//NSMutableAttributedString

NSMutableAttributedString *restName = [[NSMutableAttributedString alloc] initWithString:self.attributionText.text];

[restName addAttributes:@{ NSTextEffectAttributeName : NSTextEffectLetterpressStyle, NSFontAttributeName: [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline] } range:NSMakeRange(0, restName.length)];

self.attributionText.attributedText = restName;

12)                  UIGesture

13)                  iCloud

14)                  Library.a

15)                  Instruments

16)                  Checklist App

17)                  UICollectionView & UIContainerView & SplitVC & Popover

18)                  SlideUp

19)                  Raised Tabbar

20)                  Passbook

21)                  AR

22)                  Export Data

23)                  TF

24)                  iAds & AppiRater

25)                  TimeCmoparator – String Parsing

26)                  UIApplicationDelegate sharedApplication & its methods

27)                  NSUserDefaults

// call the store object

NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];

// set an object for it

[prefs setObject:imageView.image forKey:@”keyToLookupImage”];

// call sync to save it

[prefs synchronize];

NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];

UIImage *retrievedImage = [prefs stringForKey:@”keyToLookupImage”];

imageView.image = retrievedImage;

28)                  Façade design pattern

29)                  Protocols & Delegates

30)                  InAppPurchases

31)                  Datepicker/uipicker

32)                  NSOperation

33)                  Date Formatter

NSDate *date = [NSDate date];

NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];

[dateFormatter setDateStyle:NSDateFormatterShortStyle];

[dateFormatter setTimeStyle:NSDateFormatterFullStyle];

NSString *formattedDateString = [dateFormatter stringFromDate:date];

NSLog(@”formattedDateString for locale %@: %@”,

[[dateFormatter locale] localeIdentifier], formattedDateString);

timeLabel.text = [dateFormatter stringFromDate:date];

34)                  Letterpress Effect

35)                  asd

iOS7 Sprite Kit for Game Design for iPhone & iPad

iOS 7 Series – Sprite Kit

Welcome to iOS7 and to start off, I want to kick things off with SpriteKit.  Although it deals with video games, many companies are using iOS apps as a marketing tactic to engage their users in an effort to promote their products.

SpriteKit is the most prominent feature in iOS7 so we’re going to take a quick tour.  Go ahead and create a New Project in XCode5 and select the SpriteKit template (the bottom right icon):

iOS7 SpriteKit Basics by Marcio Valenzuela Santiapps.com
iOS7 SpriteKit Basics by Marcio Valenzuela Santiapps.com

Click next and fill in your project data.  Once you are in the main XCode window notice we have the following files in the Project Navigator:

1)   AppDelegate

2)   Main.Storyboard

3)   ViewController

4)   MyScene

If you look in the AppDelegate you will see nothing new, just a UIWindow property.  The Main.Storyboard is even more eerie.  It’s nothing more than a blank scene.  Then you find the ViewController.h file, which basically imports the SpriteKit framework.  If you check in your Target Linked Libraries you can see SpriteKit in linked.

ViewController.m is where things get interesting.

– (void)viewDidLoad

{

[super viewDidLoad];

// Configure the view.

SKView * skView = (SKView *)self.view;

skView.showsFPS = YES;

skView.showsNodeCount = YES;

// Create and configure the scene.

SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];

scene.scaleMode = SKSceneScaleModeAspectFill;

// Present the scene.

[skView presentScene:scene];

}

In viewDidLoad we create an SKView object and assign our viewcontroller’s view to it.  As an SKView type view, we have a property that allows us to display the fps onscreen and another one for viewing the nodes onscreen for debugging purposes.

We now create a second object of type SKScene and instantiate it with the size of our SKView object to make sure it fits.  Then we set the SKScene object’s scaleMode to AspectFill.  Finally we tell our SKView object to present our SKScene object each with its set properties.

There are a couple of extra methods that tell the app to autorotate and returns the supported orientations.

Now let’s move on to the MyScene object created because this is where the magic happens.

-(id)initWithSize:(CGSize)size {

if (self = [super initWithSize:size]) {

/* Setup your scene here */

self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];

SKLabelNode *myLabel = [SKLabelNode labelNodeWithFontNamed:@”Chalkduster”];

myLabel.text = @”Hello, World!”;

myLabel.fontSize = 30;

myLabel.position = CGPointMake(CGRectGetMidX(self.frame),

CGRectGetMidY(self.frame));

[self addChild:myLabel];

}

return self;

}

First we create an instance of self with the passed in value for size.  Next we set the background color and create a label in order to position it in the middle of the screen.

The next method reacts to screen touches using the well known touchesBegan method.

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

/* Called when a touch begins */

for (UITouch *touch in touches) {

CGPoint location = [touch locationInNode:self];

SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:@”Spaceship”];

sprite.position = location;

SKAction *action = [SKAction rotateByAngle:M_PI duration:1];

[sprite runAction:[SKAction repeatActionForever:action]];

[self addChild:sprite];

}

}

Here we loop through any touches and get their location as a CGPoint.  Then we instantiate a sprite from a file named Spaceship and set its position to the touch received.  Finally we create a rotating action and run it on the sprite and add that now rotating sprite to our SKScene.  Voila.

However, there is a very important method left out, the update method.  This method gets called every so often, many times a second actually!  So this is the method used to execute our game logic.

So let’s assume we are the Head iOS Programmer for a big soda company and you met with the Marketing Director about making a game to promote your product.  You decide game will be really simple, such as trying to catch a soda can falling from the sky at the beach.  What you need:

a)    A beach backdrop

b)   A soda can

c)    A potential client

d)   You also need a good marketing strategy to go with the game, otherwise playing the game won’t amount to much.  Such a strategy could be whoever makes the most points in the next month, gets a free 24pack or something.  But you better check with your Marketing Director first J

iOS7 SpriteKit Basics by Marcio Valenzuela Santiapps.com
iOS7 SpriteKit Basics
iOS7 SpriteKit Basics by Marcio Valenzuela Santiapps.com
iOS7 SpriteKit Basics
iOS7 SpriteKit Basics by Marcio Valenzuela Santiapps.com
iOS7 SpriteKit Basics Sprites

So let’s go ahead and google some images:

Ok so let’s plop these on screen to make sure the sizes are proportional.  We are going to be making this project for an iPhone Retina screen so the correct beach size should be the complete screen size, which is 960×640.  To put the beach image on the screen, aside from importing it into your project:

iOS7 SpriteKit Basics by Marcio Valenzuela Santiapps.com
iOS7 SpriteKit Basics

We need to add the code.  So let’s move over to our MyScene initWithSize method because that is only called once and we don’t need to change the background for this game after its been created the first time.  First remove the entire code inside the if block and replace it:

SKSpriteNode *background = [SKSpriteNode spriteNodeWithImageNamed:@”beach.png”];

background.anchorPoint = CGPointZero;

background.position = CGPointZero;

[self addChild:background];

This created a sprite called background using the beach image.  It positions the image and adds it to the scene.  If you run it you’ll get a chopped off image because the simulator is positioned vertically.  This is because we didn’t tell the app to only run in landscape mode.  So head over to your Target Settings and uncheck all orientations except Landscape Left.

iOS7 SpriteKit Basics by Marcio Valenzuela Santiapps.com
iOS7 SpriteKit Basics

FIRST NOTE: Orientation must be defined.

Now run it again and you’ll get a huge image as the background and possibly only part of it being displayed.  This is because you must tell Xcode that the image is to be used in Retina devices.  You do this by renaming the images beach@2x.png.  So go ahead and rename the image in Finder and you’ll have to remove the reference and re-add it as beach@2x.png.  Voila!

iOS7 SpriteKit Basics by Marcio Valenzuela Santiapps.com
iOS7 SpriteKit Basics

Ok so far so good!  Notice we get the node and fps count on the right bottom corner.

NOTE: Retina images are named filename@2x.png

Let’s go ahead and place the cooler on the screen to see if it looks right.  This time remember to rename your images before importing them.  After importing it, let’s add the following code, again in the initWithSize method because we only need to add the cooler once:

// ADD COOLER

SKSpriteNode *cooler = [SKSpriteNode spriteNodeWithImageNamed:@”cooler@2x.png”];

cooler.anchorPoint = CGPointZero;

cooler.position = CGPointZero;

[self addChild:cooler];

Just for your reference, my cooler png is 125×131, almost a square.  I added it and it looks fine:

iOS7 SpriteKit Basics by Marcio Valenzuela Santiapps.com
iOS7 SpriteKit Basics

As you can see, node count went up to 2 and fps has no reason to change so far.  There is something important to note here, and it’s that the cooler was added in the bottom left corner.  If you review the code, we set both the background and the cooler’s anchorPoint to CGPointZero, which is equal to 0,0.  This corresponds to the bottom left of the screen.  Thus when we position the cooler, we are telling it to put the cooler’s bottom left corner (its anchor), in the screen’s bottom left corner (its position).

NOTE: CGPointZero in SpriteKit = (0,0) = Bottom Left Corner

Ok now let’s think about the logic.  Both the background and the cooler need only appear once.  The soda can however might need to appear more than once because once the user catches the first one, we probably want to give him a second one to catch, otherwise it’ll be a short and disappointing game.  This is where the game structure gets interesting.  We won’t simply add the soda can in the initWithSize method but rather outsource its creation to a separate method.  Let’s create a method called createSodaCan:

-(void)createSodaCan{

// ADD CAN

SKSpriteNode *can = [SKSpriteNode spriteNodeWithImageNamed:@”sodacan@2x.png”];

can.anchorPoint = CGPointMake(CGRectGetMidX(can.frame), CGRectGetMidY(can.frame));

can.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));

[self addChild:can];

}

Here we are setting the can’s anchor point to its center and placing it in the center of the screen using CGPointMake and CGRectGetMidX/Y.  However if you run the app there won’t be a soda can anywhere onscreen because we haven’t called it.  One place to call it would be in a method such as the touchesBegan method, but let’s try something different.  In your initWithSize method, add the following code right after the cooler code:

//Delay to call can method

[NSTimer scheduledTimerWithTimeInterval:2.0

target:self

selector:@selector(createSodaCan)

userInfo:nil

repeats:NO];

This basically calls the createSodaCan method after 2 seconds of initializing the scene.  And two seconds after, there it is:

iOS7 SpriteKit Basics by Marcio Valenzuela Santiapps.com
iOS7 SpriteKit Basics

NOTE: Game logic can be fired off from the init method of your scene or from a user interaction method such as the touchesBegan method.

Great!  Now let’s think of what we want to do.  Im thinking we could do something like:

1)   Center the cooler at the bottom of the screen

2)   Move the soda can up to the top

3)   Have the can move from top to bottom

Ok great, so let’s do the first two, which should be quite simple.  Then we’ll worry about moving the can.  The first step is the cooler, so replace the cooler code with:

// ADD COOLER

SKSpriteNode *cooler = [SKSpriteNode spriteNodeWithImageNamed:@”cooler@2x.png”];

cooler.anchorPoint = CGPointMake(CGRectGetMidX(cooler.frame), CGRectGetMidY(cooler.frame));

cooler.position = CGPointMake(self.frame.size.width*0.5, self.frame.size.height*0.01);

[self addChild:cooler];

This will place the cooler at 50% the width of the screen and about 1% from bottom to top.  Now do something similar with the can except that we want it to be on top so it can drop:

// ADD CAN

SKSpriteNode *can = [SKSpriteNode spriteNodeWithImageNamed:@”sodacan@2x.png”];

can.anchorPoint = CGPointMake(CGRectGetMidX(can.frame), CGRectGetMidY(can.frame));

can.position = CGPointMake(self.frame.size.width/2, self.frame.size.height*0.75);

[self addChild:can];

This places the can halfway across from left to right and ¾ of the way up the screen:

iOS7 SpriteKit Basics by Marcio Valenzuela Santiapps.com
iOS7 SpriteKit Basics

NOTE: We use relative positioning of an object in the view’s frame to avoid offscreen or misplaced images on different sized screens.

Perfect!  Now how do we animate the can so it can fall?  Well there are two ways, one is using physics, which requires us to create a physics-body object and assign it to the soda can and then “turn on” gravity so to speak.  The much simpler way is to animate the can to go from the top of the screen to the bottom of the screen.

To use the latter, we need SKActions much like the rotate action in the template.  So let’s code some and see what happens.  Well I took a look at the way the template created the rotating action and started typing…XCode took care of the rest.  I reasoned that what I wanted was to “move” the sprite and XCode suggested the following:

//Move the can

SKAction *fallingAction = [SKAction moveByX:<#(CGFloat)#> y:<#(CGFloat)#> duration:<#(NSTimeInterval)#>]

So I went with that and ended up with this code for the createSodaCan method:

// ADD CAN

SKSpriteNode *can = [SKSpriteNode spriteNodeWithImageNamed:@”sodacan@2x.png”];

can.anchorPoint = CGPointMake(CGRectGetMidX(can.frame), CGRectGetMidY(can.frame));

can.position = CGPointMake(self.frame.size.width/2, self.frame.size.height*0.75);

//Move the can

SKAction *fallingAction = [SKAction moveByX:0 y:-400 duration:2.0];

[can runAction:fallingAction];

[self addChild:can];

Well that’s not half bad.  I could’ve used another method such as moveTo instead of moveBy.

Ok now let’s simulate how to actually “catch” the can in the cooler.  What we need is something called “collision detection”.  What we want to do is check to see if the rectangle around the cooler is intersecting with the rectangle around the can.  To do this, we have to do so constantly.  That’s where the update method comes in.  This method is called repeatedly before each frame is drawn.  So we need to ask it if those two rectangles are colliding.  That means we need references to both objects.  To do that we create an instance variable for each, so we add them in MyScene.h:

@interface MyScene : SKScene {

SKSpriteNode *cooler;

SKSpriteNode *can;

}

Now modify the code in initWithSize by removing the type declaration in each line where we create the cooler and the can so they look like:

cooler = [SKSpriteNode spriteNodeWithImageNamed:@”cooler@2x.png”];

can = [SKSpriteNode spriteNodeWithImageNamed:@”sodacan@2x.png”];

And now we can access those objects through their pointers in the update method as well.  So add this line to your update method:

//Check for intersection of frames for collision simulation

[self testCollisionOfSprite:can withSprite:cooler];

This means this line will be called every time the update method fires.  You can even add an NSLog into the update method to get an idea of how many times its called.  Now let’s define the testCollisionOfSprite:withSprite: method:

-(void)testCollisionOfSprite:(SKSpriteNode*)sprite1 withSprite:(SKSpriteNode*)sprite2{

CGRect sprite1Rect = [self getSizeOfSprite:sprite1];

CGRect sprite2Rect = [self getSizeOfSprite:sprite2];

if (CGRectIntersectsRect(sprite1Rect, sprite2Rect)) {

NSLog(@”They collided”);

}

}

Nothing special going on here, we are simply creating a rectangle for each of the 2 sprites passed in and checking if those rectangles intersect.  If they do, we log.  Finally the way we get those rectangles around the sprites:

– (CGRect) getSizeOfSprite:(SKSpriteNode*)sprite{

double sx = sprite.position.x;

double sy = sprite.position.y;

return CGRectMake(sx, sy, sprite.frame.size.width, sprite.frame.size.height);

}

We have a game…almost.  Let’s add some sound and make the can disappear.

Import your favorite sound and add this code right after the NSLog for They Collided:

SKAction *play = [SKAction playSoundFileNamed:@”explosion_small.caf” waitForCompletion:YES];

[can runAction:play];

can.alpha = 0;

As you noticed earlier, the collision happens many times because there is a long trajectory form the moment the rectangles begin overlapping until they stop overlapping.  This results in a repetitive sound, which is annoying.  More importantly if becomes a problem if you want to work with scoring points because if you try to +1 a point when the collision occurs, you actually end up with many more points in total!  So what is usually done in these cases is a work around that creates a BOOL flag from the start and sets it to False.  We do this in MyScene.h:

BOOL collisionOccurred;

then in the createSodaCan method we set it to FALSE:

collisionOccurred = FALSE;

Finally we modify the if test like so:

if (CGRectIntersectsRect(sprite1Rect, sprite2Rect) && !collisionOccurred) {

collisionOccurred = TRUE;

NSLog(@”They collided”);

SKAction *play = [SKAction playSoundFileNamed:@”explosion_small.caf” waitForCompletion:YES];

[can runAction:play];

can.alpha = 0;

}

This way we always check to see if collisionOccurred is FALSE, if it is then we set it to TRUE inside the if, do our stuff, thus collisionOccurred will not be FALSE again until we set it back.

It is a cumbersome solution because then you must make sure you re-set your flag to FALSE everytime you create a new can.  It even worse if you don’t really create more cans.  Many times for memory saving reasons, we simply don’t destroy the original can.  If you noticed I didn’t destroy the node, I simply made it invisible.  The reason is that now I can reuse that sprite again by repositioning it on top and making it visible again thus saving processor power.  This is a very common tactic in games where you want to have lots of enemies or coins or what not and you don’t want to have to create thousands of instances of them.

The other more elegant solution to the collision detection repetition issue is the first method we talked about collision detection.  So let’s try to cover that in as simple terms as possible.

Physics & Collision Detection

Physics introduces the notion of physical bodies that will be “attached” to our sprite nodes.  Those bodies can be of type dynamic, static or edges.  A dynamic body reacts to physics, static bodies react to physics except forces and collisions because they are meant not to move yet exist in a physical world.  Edges never move and they are usually used to represent boundaries in a game.

iOS7 SpriteKit Basics by Marcio Valenzuela Santiapps.com
iOS7 SpriteKit Basics

This means that we can try to match a physics body to a sprite node identically, such as:

iOS7 SpriteKit Basics by Marcio Valenzuela Santiapps.com
iOS7 SpriteKit Basics

which results in a very complex physics body to monitor vs simply using a tall rectangle which results in a much easier body to manipulate.  A circle is actually the easiest body to use.

So let’s start off by creating a border around our screen by adding this line at the end of our initWithSize method:

//Create border

self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];

and in the createSodaCan method add these lines to the end:

can.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:can.size.width/2];

can.physicsBody.dynamic = YES;

What we have done is create a second physicsBody for the can this time and made it of circular shape and assigned it a dynamic property.  Now let’s remove the line in the update method that calls for testCollisionOfSprite:withSprite: because we don’t need that anymore.  Now run the app and you should see the soda can actually stop near the bottom of the screen which is where the border should be.  But the border is invisible, isn’t it?  Go ahead and comment out the border line in the init method and see for yourself J.

Ok so now let’s remove our line that made the can move from top to bottom:

//[can runAction:fallingAction];

And now let gravity do the work for you, add this line at the end of your initWithSize method:

//Set Gravity

self.physicsWorld.gravity = CGPointMake(0.0, -9.8);

Cool!  But the can didn’t bounce quite so much huh!?  Well that’s probably what a can would do.  But just for fun, let’s say it was a rubber can.  Add this line after the can’s dynamic physicsBody setting to YES:

can.physicsBody.restitution = 0.5;

Neat!  Well but we’re not gonna be covering physics now.  So turn that property off for now.  We just need physics for collision detection.  This is done via collision groups.

First we adopt the SKPhysicsContactDelegate Protocol so we can set out scene as the delegate to receive notifications of collisions:

@interface MyScene : SKScene <SKPhysicsContactDelegate>{

First we set our scene as the delegate in the initWithSize method:

self.physicsWorld.contactDelegate = self;

Now we must set the categories in our can and cooler objects.  So after the cooler sprite creation in the initWithSize method, add this code to create a physics body for it and set its category mask:

//collision physics

cooler.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:cooler.size];

cooler.physicsBody.dynamic = YES;

cooler.physicsBody.categoryBitMask = coolerCategory;

cooler.physicsBody.collisionBitMask = canCategory;

cooler.physicsBody.contactTestBitMask = canCategory;

So we create a rectangular body instead of a circle and set its category to cooler but its collision mask to canCategory.

Likewise in the can creation method (createSodaCan) we use this code:

//Add physics body to can

can.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:can.size.width/2];

can.physicsBody.dynamic = YES;

//can.physicsBody.restitution = 0.5;

can.physicsBody.categoryBitMask = canCategory;

can.physicsBody.collisionBitMask = coolerCategory;

can.physicsBody.contactTestBitMask = coolerCategory;

As mentioned before, we set this body as a circle but commented out the restitution property for now.  We set its category to can and its collision mask to cooler.  We must define those categories above the @implementation line like so:

//DONT COLLIDE

static const uint32_t canCategory =  0x1 << 0;

static const uint32_t coolerCategory =  0x1 << 1;

Finally, we override the beginContact method of the protocol which reads:

//COLLISION PHYSICS

– (void)didBeginContact:(SKPhysicsContact *)contact

{

SKPhysicsBody *firstBody, *secondBody;

if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)

{

firstBody = contact.bodyA;

secondBody = contact.bodyB;

}

else {

firstBody = contact.bodyB;

secondBody = contact.bodyA;

}

if ((firstBody.categoryBitMask & coolerCategory) != 0)

{

NSLog(@”They collisionated!”);

}

}

We pass in a contact, then create 2 body references which will come in as part of that contact.  We set the contact bodies to either one and test for a collision and log something in the console.

Well this is as far as SpriteKit basics can get.  There are more advanced topics such as advanced physics, forces, particles, animations, parallax and raycasting that could be material for a Part 2 should there be enough interest in the subject.