Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Photo Storage iOS App

edited May 2012 in Tutorials

Introduction

In this tutorial, we'll be learning how to store photos in Kumulos's backend as a service. This is a common requirement of many mobile apps, and this simple example can be reused throughout many of your app development projects. It can also be adapted to use for general file storage in Kumulos. Here's a rough idea of how our finished app will look:

Finished app

To follow along, you should watch the video tutorial and set up the Kumulos backend service to support your iOS app. After that, you'll be ready to download the XCode project and get stuck into the app integration!


Get Resources

XCode project
Test cat
Test cat from placekitten

Acknowledgements

We'd like to thank Simon from Maybelost.com for allowing us to adapt his excellent tutorial for creating a photo storage app. If you're interested in learning how to create this app from scratch, it's definitely worth checking out his Storyboard app with Core Data tutorial.

The Kumulos Bit

The iPhone Bit

It’s worth noting that this tutorial makes use of the Camera on iPhone. If you don’t have a camera, don’t worry – it’ll still work, but you’ll need to select images from an album. Also note that NO checking has been done to see if the camera is available, so if you select the option and you don’t have a camera – it WILL crash!

Anyway, to get started, download the sample project and open it up in XCode. Then add the Kumulos libraries into the project as shown at the end of the video. Once the libraries have been added, we need to include the Kumulos header file in our app. So, in both the PictureListMainTable.h and PictureListDetail.h, uncomment the import statements:

#import "Kumulos.h"
Now we can get to work!

Listing Pictures

The first thing we're going to do is set up our main table to retrieve records from Kumulos and show them in a list. In PictureListMainTable.m edit the viewDidLoad method so it initialises a Kumulos object like so:

-(void)viewDidLoad
{
//init kumulos
k = [[Kumulos alloc] init];
[k setDelegate:self];

//init array of objects
pictureListData = [[NSMutableArray alloc] init];
}
Note that the delegate is set to the PictureListMainTable instance, and this is how we will handle the results of a call to Kumulos for the images. An NSMutableArray is initiated to hold the pictures from Kumulos that will be shown in the table.

With initialisation complete, let's actually make the call to Kumulos. We're going to retrieve the records from Kumulos every time this view is shown to the user. To do this, edit your viewWillAppear method:

-(void)viewWillAppear:(BOOL)animated
{
//get the data from k
[k getImagesWithData];
}
Here we call our API method created in Kumulos, getImagesWithData, which will select all of the images with their image data.

In order to handle the response, we need to implement the KumulosDelegate protocol. Each method you call in Kumulos will have a corresponding delegate method that will handle its results. This allows for asynchorous operations.

At the end of PictureListMainTable.m, there's a delegate method which we can fill in as follows:

-(void)kumulosAPI:(Kumulos *)kumulos apiOperation:(KSAPIOperation *)operation getImagesWithDataDidCompleteWithResult:(NSArray *)theResults
{
//empty the list and re-create it
[pictureListData removeAllObjects];
[pictureListData addObjectsFromArray:theResults];

[self.tableView reloadData];
}
When the call to getImagesWithData completes, this delegate method will be passed an NSArray of results from Kumulos. Here we populate the member variable pictureListData with this array, and then reload the table view with this data.

When the table data is reloaded, we need to draw each cell that will be displayed based on which row it is. The following method will be used by the SDK to ask you for a particular table cell. We make it render the cell based on the NSMutableArray of picture data.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}

// Fill in the cell contents
cell.textLabel.text = [[pictureListData objectAtIndex:indexPath.row] valueForKey:@"title"];
cell.detailTextLabel.text = [[pictureListData objectAtIndex:indexPath.row] valueForKey:@"description"];

// If a picture exists then use it
if ([[pictureListData objectAtIndex:indexPath.row] valueForKey:@"imageData"])
{
NSArray *imageData = [[pictureListData objectAtIndex:indexPath.row] valueForKey:@"imageData"];
cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
cell.imageView.image = [UIImage imageWithData:[imageData valueForKey:@"data"]];
}

return cell;
}
We populate the labels for title and description from the NSDictionary stored in the pictureListData array at index indexPath.row. Then we check if we've got picture data included, and if so, we create a UIImage from the data stored in Kumulos, and we set the imageView to use this image.

So now we're all set to display pictures from Kumulos, but there are no pictures! So, next we'll look at how to add pictures.

Adding Pictures

The addition and editing of pictures is handled bu the PictureListDetail.m file. So now we open that file, and initialise a Kumulos object in the viewDidLoad:

- (void)viewDidLoad
{
[super viewDidLoad];

k = [[Kumulos alloc] init];
[k setDelegate:self];
editing = NO;
}
As earlier, this will initialise a Kumulos object to handle our image save requests. The editing flag is used to determine whether we should create a new image, or update an existing one.

Now we're ready to handle when the save button is actually pressed. This is done in the button handler:

- (IBAction)editSaveButtonPressed:(id)sender
{
// Commit item - updating if editing, otherwise create a new entry
if(!editing)
{
[k createImageWithTitle:[titleField text] andDescription:[descriptionField text] andImageData: [self getResizedImageData]];
}
else
{
// TODO - Update existing image details
}

}
When we're not editing, we call our createImage API method and pass in the title, description, and a resized image. When this call to save the image completes, we should go back to the picture list view in order to see the picture. This is done in the delegate for completing the operation:

-(void)kumulosAPI:(Kumulos *)kumulos apiOperation:(KSAPIOperation *)operation createImageDidCompleteWithResult:(NSNumber *)newRecordID
{
//Automatically pop to previous view now we're done adding
[self.navigationController popViewControllerAnimated:YES];
}
Now when we run the app, we should be able to create pictures and see them showing up in the list! Fantastic. Now all that's left is image editing and some final thoughts.

Editing Pictures

We want to be able to edit existing pictures by selecting them from the list, and saving the updated details to Kumulos. In order to do this, we first have to detect when a user selects a row from the table view. We then set up the PitureListDetail view for editing the item.

So, in PictureListMainTable.m, we need to implement the prepareForSegue method:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get a reference to our detail view
PictureListDetail *pld = (PictureListDetail *)[segue destinationViewController];

// If we are editing a picture we need to pass some stuff, so check the segue title first
if ([[segue identifier] isEqualToString:@"EditPicture"])
{
// Get the row we selected to view
NSInteger selectedIndex = [[self.tableView indexPathForSelectedRow] row];

// Pass the picture object from the table that we want to view
pld.currentPicture = [pictureListData objectAtIndex:selectedIndex];
}
}
Segues are a new concept with the Storyboard builder, and are essentially transitions between screens in apps. In our example, the PictureListMainTable view is set up to transition to the PictureListDetail view when a row is selected. The prepareForSegue method lets us do any necessary setup before handing over to the new view controller.

In this case, se get a reference to the object at the row we selected, and pass it to the PictureListDetail view controller.

Now we can open PictureListDetail.m and fill in the editing code. In viewDidLoad, we add:

if (currentPicture)
{
editing = YES;

[titleField setText:[currentPicture valueForKey:@"title"]];
[descriptionField setText:[currentPicture valueForKey:@"description"]];

//loading image needs sorted
NSArray *imageData = [currentPicture valueForKey:@"imageData"];
if([[imageData valueForKey:@"data"] length] > 0)
{
imageField.contentMode = UIViewContentModeScaleAspectFit;
[imageField setImage:[UIImage imageWithData:[imageData valueForKey:@"data"]]];
}
}
This simply fills in our text entry fields and sets the image view to the correct image.

Next we can update our editSaveButtonPressed handler so that it correctly saves any changes the user makes by issuing Kumulos calls:

- (IBAction)editSaveButtonPressed:(id)sender
{
// Commit item - updating if editing, otherwise create a new entry
if(!editing)
{
[k createImageWithTitle:[titleField text] andDescription:[descriptionField text] andImageData: [self getResizedImageData]];
}
else
{
NSNumber *ID = [NSNumber numberWithInt:[[currentPicture valueForKey:@"imageID"]intValue]];
[k updateImageDataWithData:[self getResizedImageData] andMetadata:[ID intValue]];
[k updateImageMetadataWithTitle:[titleField text] andDescription:[descriptionField text] andImageID:[ID intValue]];
}

}
Now all that's left to do is allow the user to delete an image from the table. We'll be using the familiar iOS swipe to delete interaction, and it's set up in PictureListMainTable.m:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
// Remove from kumulos using the ID
[k deleteImageWithImageID:[[[pictureListData objectAtIndex:indexPath.row] valueForKey:@"imageID"] intValue]];

// Remove the item from our array
[pictureListData removeObjectAtIndex:indexPath.row];

// Delete the row from the data source
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
And that's it! Our app is feature complete. Give it a run to see the results of your effort, and start storing those cat pictures!

Final Thoughts

This example, whilst relatively simple, shows you how to leverage the power of the cloud for your mobile apps that need a backend.

You could easily add users to this and have them able to store and share their own photos rather than just having a global photo collection.

If you've any feedback about this, or requests for what else you'd like to see, just let us know!

Comments

  • edited January 2013
    Great tutorial. Thanks very much.

    Xcode is so variant that I've noticed how hard it is to write portable tutorial code that "just runs," but you've gotten about as close as possible. I had absolutely no hiccups with any of the logic or syntax... it just ran. Nicely done.

    For the benefit of others, I wanted to add some configuration hurdles I had to cross:
    1) To get past "Undefined symbols for architecture i386... I had to add kumulos.m to [project]>Targets>Build Phases>Compile Sources.
    2) To get past "Undefined symbols for architecture armv7:) I had to add my .a file (Kumulos_ARMv7-Combined.a) to [project]>Targets>Build Phases>Link Binary With Libraries.
    3) When building to devices (not simulator), I got a codesigning errror:"CodeSign error: code signing is required for product type 'Application' in SDK 'iOS 6.0'. I had to change "[project]>Targets>Build Settings>Build active architecture only" from No to Yes, then set Build active architecture only>Release to "No". The Release build was unintentional and would presumably require a distribution profile.

    Of course it's always possible these are just my circumstances, not tied to the code. The complexity of the Xcode environment makes this very difficult to determine... especially for someone who finds himself doing a tutorial. :)
Sign In or Register to comment.