Skip to content
This repository has been archived by the owner on Mar 9, 2022. It is now read-only.

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
snej committed Aug 18, 2011
0 parents commit 89b3e75
Show file tree
Hide file tree
Showing 17 changed files with 1,900 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.DS_Store
*.swp
*.pbxuser
*.perspectivev3
*.mode1v3
xcuserdata/
build/
Documentation/
Frameworks/

39 changes: 39 additions & 0 deletions Classes/Event.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

/*
File: Event.h
Abstract: A class to represent an event containing geographical coordinates, a time stamp, and an image with a thumbnail. Every Event has a CouchDB document.
Version: 1.1
*/

#import <CoreLocation/CoreLocation.h>
@class CouchDatabase, CouchDocument;

@interface Event : NSObject {
CouchDocument* document;
UIImage* photo;
UIImage* thumbnail;
BOOL checkedForPhoto;
}

/** Creates a brand-new event and adds a document for it to the database. */
- (id)initWithDatabase:(CouchDatabase*)database
latitude:(CLLocationDegrees)latitude
longitude:(CLLocationDegrees)longitude
creationDate:(NSDate*)creationDate;

/** Instantiates an Event object for an already-existing document. */
- (id)initWithDocument:(CouchDocument*)document;

/** Deletes the event's document from the database permanently. */
- (void) deleteDocument;

@property (nonatomic, readonly) NSDate *creationDate;
@property (nonatomic, readonly) NSNumber* latitude;
@property (nonatomic, readonly) NSNumber* longitude;

@property (nonatomic, retain) UIImage *photo;
@property (nonatomic, readonly) UIImage *thumbnail;

@end

166 changes: 166 additions & 0 deletions Classes/Event.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@

/*
File: Event.m
Abstract: A class to represent an event containing geographical coordinates, a time stamp, and an image with a thumbnail. Every Event has a CouchDB document.
Version: 1.1
*/

#import "Event.h"

#import <CouchCocoa/CouchCocoa.h>
#import <CouchCocoa/RESTBody.h>


static UIImage* MakeThumbnail(UIImage* image, CGFloat dimensions);


@implementation Event


- (id)initWithDatabase:(CouchDatabase*)database
latitude:(CLLocationDegrees)latitude
longitude:(CLLocationDegrees)longitude
creationDate:(NSDate*)creationDate {
self = [super init];
if (self) {
NSDictionary* properties = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithDouble: latitude], @"latitude",
[NSNumber numberWithDouble: longitude], @"longitude",
[RESTBody JSONObjectWithDate: creationDate], @"creationDate",
nil];
document = [[database untitledDocument] retain];
RESTOperation* op = [document putProperties: properties];
if (![op wait]) {
// TODO: report error
NSLog(@"Creating Event document failed! %@", op.error);
}
}
return self;
}


- (id)initWithDocument:(CouchDocument*)aDocument {
self = [super init];
if (self) {
document = [aDocument retain];
}
return self;
}


- (void)dealloc {
[document release];
[thumbnail release];
[super dealloc];
}


- (void) deleteDocument {
[[document DELETE] start];
[document release];
document = nil;
}


- (NSDate*) creationDate {
NSString* dateString = [document.properties objectForKey: @"creationDate"];
return [RESTBody dateWithJSONObject: dateString];
}


- (NSNumber*) latitude {
return [document.properties objectForKey: @"latitude"];
}


- (NSNumber*) longitude {
return [document.properties objectForKey: @"longitude"];
}


- (UIImage*)thumbnail {
// The thumbnail is stored inline as Base64-encoded data, because it's small.
if (!thumbnail) {
NSString* thumbnailBase64 = [document.properties objectForKey: @"thumbnail"];
NSData* thumbnailData = [RESTBody dataWithBase64: thumbnailBase64];
if (thumbnailData) {
thumbnail = [[UIImage alloc] initWithData: thumbnailData];
}
}
return thumbnail;
}


- (UIImage*)photo {
// The photo is stored as an attachment, so it is fetched separately on demand.
if (!checkedForPhoto) {
checkedForPhoto = YES;
CouchAttachment* attach = [document.currentRevision attachmentNamed: @"photo"];
RESTOperation* op = [attach GET];
if ([op isSuccessful]) {
NSData* imageData = op.responseBody.content;
photo = [[UIImage alloc] initWithData: imageData];
}
}
return photo;
}


- (void)setPhoto:(UIImage *)newPhoto {
if (checkedForPhoto && newPhoto == photo)
return;
[photo release];
photo = [newPhoto retain];
checkedForPhoto = YES;

// Update the thumbnail image:
[thumbnail release];
thumbnail = nil;
NSData* thumbnailData = nil;
if (newPhoto) {
thumbnail = [MakeThumbnail(newPhoto, 44) retain];
thumbnailData = UIImageJPEGRepresentation(thumbnail, 0.5);
}

// Save the thumbnail inline:
NSMutableDictionary* properties = [[document.properties mutableCopy] autorelease];
[properties setValue:[RESTBody base64WithData:thumbnailData] forKey:@"thumbnail"];
RESTOperation* op = [document putProperties:properties];
if (![op wait]) {
// TODO: Report error
NSLog(@"Saving Event document failed! %@", op.error);
}

// Save the photo to an attachment, asynchronously:
CouchAttachment* attach = [document.currentRevision createAttachmentWithName:@"photo"
type:@"image/png"];
if (newPhoto) {
op = [attach PUT: UIImagePNGRepresentation(newPhoto) contentType: @"image/png"];
} else {
op = [attach DELETE];
}
[op onCompletion: ^{
if (!op.isSuccessful) {
// TODO: Report error
NSLog(@"Saving Event photo attachment failed! %@", op.error);
}
}];
}


@end


/** Makes a shrunk-down copy of an image. */
static UIImage* MakeThumbnail(UIImage* image, CGFloat dimensions) {
CGSize size = image.size;
CGFloat ratio = dimensions / MAX(size.height, size.width);
CGRect rect = CGRectMake(0.0, 0.0, ratio * size.width, ratio * size.height);

UIGraphicsBeginImageContext(rect.size);
[image drawInRect:rect];
UIImage* thumbnail = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return thumbnail;
}
30 changes: 30 additions & 0 deletions Classes/EventDetailViewController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

/*
File: EventDetailViewController.h
Abstract: The table view controller responsible for displaying the time, coordinates, and photo of an event, and allowing the user to select a photo for the event, or delete the existing photo.
Version: 1.1
*/

@class Event;

@interface EventDetailViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate> {
Event *event;
UILabel *timeLabel;
UILabel *coordinatesLabel;
UIButton *deletePhotoButton;
UIImageView *photoImageView;
}

@property (nonatomic, retain) Event *event;

@property (nonatomic, retain) IBOutlet UILabel *timeLabel;
@property (nonatomic, retain) IBOutlet UILabel *coordinatesLabel;
@property (nonatomic, retain) IBOutlet UIButton *deletePhotoButton;
@property (nonatomic, retain) IBOutlet UIImageView *photoImageView;

- (IBAction)choosePhoto;
- (IBAction)deletePhoto;
- (void)updatePhotoInfo;

@end
127 changes: 127 additions & 0 deletions Classes/EventDetailViewController.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@

/*
File: EventDetailViewController.m
Abstract: The table view controller responsible for displaying the time, coordinates, and photo of an event, and allowing the user to select a photo for the event, or delete the existing photo.
Version: 1.1
*/

#import "EventDetailViewController.h"
#import "Event.h"


@implementation EventDetailViewController

@synthesize event, timeLabel, coordinatesLabel, deletePhotoButton, photoImageView;


#pragma mark -
#pragma mark Lifecycle

- (void)viewDidLoad {
[super viewDidLoad];

// A date formatter for the creation date.
static NSDateFormatter *dateFormatter = nil;
if (dateFormatter == nil) {
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeStyle:NSDateFormatterShortStyle];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
}

static NSNumberFormatter *numberFormatter;
if (numberFormatter == nil) {
numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
[numberFormatter setMaximumFractionDigits:3];
}

timeLabel.text = [dateFormatter stringFromDate:[event creationDate]];

NSString *coordinatesString = [[NSString alloc] initWithFormat:@"%@, %@",
[numberFormatter stringFromNumber:[event latitude]],
[numberFormatter stringFromNumber:[event longitude]]];
coordinatesLabel.text = coordinatesString;
[coordinatesString release];

[self updatePhotoInfo];
}


- (void)viewDidUnload {

self.timeLabel = nil;
self.coordinatesLabel = nil;
self.deletePhotoButton = nil;
self.photoImageView = nil;
}


#pragma mark -
#pragma mark Editing the photo

- (IBAction)deletePhoto {
[event setPhoto:nil];

// Update the user interface appropriately.
[self updatePhotoInfo];
}


- (IBAction)choosePhoto {

// Show an image picker to allow the user to choose a new photo.

UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
imagePicker.delegate = self;
[self presentModalViewController:imagePicker animated:YES];
[imagePicker release];
}



- (void)updatePhotoInfo {

// Synchronize the photo image view and the text on the photo button with the event's photo.
UIImage *image = event.photo;

photoImageView.image = image;
deletePhotoButton.enabled = (image != nil);
}


#pragma mark -
#pragma mark Image picker delegate methods

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)selectedImage editingInfo:(NSDictionary *)editingInfo {
// Update the database.
event.photo = selectedImage;
// Update the user interface appropriately.
[self updatePhotoInfo];

[self dismissModalViewControllerAnimated:YES];
}


- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
// The user canceled -- simply dismiss the image picker.
[self dismissModalViewControllerAnimated:YES];
}


#pragma mark -
#pragma mark Memory management

- (void)dealloc {

[event release];
[timeLabel release];
[coordinatesLabel release];
[deletePhotoButton release];
[photoImageView release];

[super dealloc];
}


@end
Loading

0 comments on commit 89b3e75

Please sign in to comment.