How to create a Photo App in iOS with Syncano and SDWebImage - part 2, adding cloud storage

Mariusz WisniewskiMariusz Wisniewski

In the second part of this tutorial, I'm going to teach you how to add the Syncano platform to a previously-created Photo App, to take a selfie (or just a picture), and to store it easily on Syncano!

Disclaimer: This tutorial is written in Swift 2, and has not been tested with Swift 3

Add Syncano to Selfie Taker App!

Time for another big step -- sending your photos to Syncano and storing them in there, so you can easily retrieve them later.

Install Syncano with CocoaPods

If you don't know what CocoaPods are yet -- it's a package manager, used often by iOS developers, to easily add and install frameworks and libraries made by other developers -- extending what your app can do.

You can learn more about CocoaPods from the company’s website, or from a video guide from Google.

This is the recommended way of installing Syncano and adding it to your app.

If you don't have CocoaPods yet (or you don't know if you have it), open your Terminal app and type in there

sudo gem install cocoapods  

If you didn't have CocoaPods installed before, please make sure to close the Terminal window and open a new one before proceeding to the next step -- otherwise, the pod command might not be recognized when you try to use it.

Navigate in the Terminal to the project's folder, by typing

cd /path/to/your/app  

(where /path/to/your/app is, for example, ~/MyApps/SelfieTaker/; if your app is located in MyApp in your home directory, it has to be the same folder your .xcodeproj file is in.)

Type

pod init  

After a few seconds, you will see Podfile added. You can edit it with the text editor of your choice.

Inside Podfile, add two lines with the needed libraries

pod pod 'syncano-ios'  
pod 'SDWebImage', '~>3.7'  

and uncomment the line with use_frameworks!. After these changes, your Podfile should look more less like this:

# Uncomment this line to define a global platform for your project
# platform :ios, '8.0'
# Uncomment this line if you're using Swift
use_frameworks!

target 'SelfieTaker' do  
pod 'syncano-ios'  
pod 'SDWebImage', '~>3.7'  
end  

The first line we added will let you install Syncano. The second one adds the SDWebImage framework, which we will use for image caching -- so we won't have to download the same image multiple times (if it was already downloaded before).

Now, you can install the pods you have just added. Type in the Terminal app

pod install  

Close your XCode project, and open the newly created workspace file. In my case, it's SelfieTaker.xcworkspace

open SelfieTaker.xcworkspace  

Run your project to compile and build new libraries, and make sure it works as before.

Coding -- send photos to Syncano

Add New UI to ViewController

Let's start by adding some UI in code. Add this code inside your ViewController Class.

//define activity indicator and initialize it
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge)  

We will be using this activity indicator to show our users that a photo is being uploaded. Inside your viewDidLoad method, add a few calls to set up where it will be located and how it will look.

//set up activity indicator properties - color, behavior etc.
self.activityIndicator.color = UIColor.blackColor()  
self.activityIndicator.hidesWhenStopped = true  
self.navigationItem.titleView = activityIndicator  

First, we set its color to black, set it to be hidden when it's not animating, and we add it to the title view of our top navigation bar.

Set up Syncano Connection

Open the AppDelegate.swift file to set up a Syncano connection in there for the whole app.

Add on top of your file

import syncano_ios  

and inside func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool function add

Syncano.sharedInstanceWithApiKey("3b22dd534bbdc6920b41c401a1bca837cdb98f8f", instanceName: "diamond-hack")  

(If you use this Instance, remember that everyone will have access to these photos -- so be careful about what you upload ;))

Ideally this should be your Instance name and your API Key, but for simplicity -- you can use mine for now. :)

After these changes, the top of your AppDelegate.swift file should look like this:

import UIKit  
import syncano_ios

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        Syncano.sharedInstanceWithApiKey("3b22dd534bbdc6920b41c401a1bca837cdb98f8f", instanceName: "diamond-hack")
        return true
    }
//...
}

Display Images from Syncano

Now go to the CollectionViewController.swift file and change our Photo Class to indicate that it's a Class that will be connected to Syncano.

Add on top of your file import syncano_ios, import SDWebImage (for image caching) and change the code defining your Photo Class to

class Photo : SCDataObject {  
    var name = ""
    var deviceId = ""
    var image : SCFile?
    var downloadedImage : UIImage?

    //custom init method - useful for quick creation of new objects of this type
    init(name: String, image: UIImage?) {
        self.name = name
        if image != nil {
            let data = UIImageJPEGRepresentation(image!, 0.5)
            self.image = SCFile(aData: data)
        }
        self.deviceId = Utilities.getDeviceIdentifier()
        super.init()
    }

    required init(dictionary dictionaryValue: [NSObject : AnyObject]!) throws {
        try super.init(dictionary: dictionaryValue)
    }

    required init!(coder: NSCoder!) {
        super.init(coder: coder)
    }

    override init() {
        super.init()
    }
}

What's important to notice here is that we change the Class of image variable - from UIImage to SCFile -- this way, we indicate that it's an image file stored on Syncano, not locally.
We have a new variable called downloadedImage -- we will use it to store images after they are downloaded.

We also changed the init method to compensate for that, and we added a few extra methods, to stop app from crashing. :)

Find func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell and change it to:

override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {  
   //get the cell that should be displayed
   let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath)
   //cast the cell to our cell class type - CollectionViewCell
   if let cell = cell as? CollectionViewCell {
      //get current photo
      let photo = self.photos[indexPath.row]

      //check if we already downloaded current photo
      if let data = photo.image?.data {
         //if yes, we stop loading animation and we set the image on the cell view
         cell.activityIndicator.stopAnimating()
         cell.imageView.image = UIImage(data: data)
      } else if photo.image != nil {
         //start loading animation, so user knows what's going on
         cell.activityIndicator.startAnimating()
         //we added Pod module that helps with caching - lets use it!
         //download image for give URL - it will be cached automatically
         cell.imageView.sd_setImageWithURL(photo.image?.fileURL, completed: { image, error, cacheType, url in
            //when image is downloaded, or taken from cache - stop the animation
            cell.activityIndicator.stopAnimating()
            //set the image on cell
            cell.imageView.image = image
            //save downloaded image
            photo.downloadedImage = image
         })
      }
   }
   //return configured cell
   return cell
}

Basically, the idea is the same as before -- get the reusable cell and configure it. What is new is the configuration part. Now, instead of taking the stored image, we download it from the internet. If it was already downloaded and is cached on disk, it will automatically be taken from the disk for us.

You probably noticed you have some errors highlighted in XCode -- it's because we haven't yet defined an activity indicator on the collection view cell.

Before we fix it, add some UI to make it visible when the app is downloading the images list from Syncano. Put this code on top of your Class implementation (same thing we did in ViewController Class)

let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge)  

and add this at the bottom of viewDidLoad method

self.activityIndicator.color = UIColor.blackColor()  
self.activityIndicator.hidesWhenStopped = true  
self.navigationItem.titleView = activityIndicator  

Download Images from Syncano

Add this function to your Class, to be able to download images from Syncano.

//method used to download images from Syncano
func reloadImagesFromSyncano() {  
   //before we start downloading images, start loading indicator
   self.activityIndicator.startAnimating()     
   //ask for photos from Syncano
   //every SCDataObject class (and subclasses) offers please() method - always use it to download objects
   //if you want to filter your objects, use please().giveMeDataObjectsWithPredicate
   //and SCPredicate.whereKey -- to define search criteria
   Photo.please().giveMeDataObjectsWithPredicate(SCPredicate.whereKey("deviceId", isEqualToString: Utilities.getDeviceIdentifier()), parameters: nil) { photos, error in
      //after photos were downloaded, stop loading indicator
      self.activityIndicator.stopAnimating()
      //save downloaded photos list to memory
      if let photos = photos as? [Photo] {
         self.photos = photos
      }
      //reload our view with newly downloaded images
      self.collectionView?.reloadData()
   }
}

At the beginning, we start the animated activity indicator; when the downloading is finished, we stop it (and it will hide automatically).

The main part of downloading images from Syncano is done here

Photo.please().giveMeDataObjectsWithPredicate(SCPredicate.whereKey("deviceId", isEqualToString: Utilities.getDeviceIdentifier()), parameters: nil) { photos, error in  

We say here that we want to download Photo Objects from Syncano. Using SCPredicate we ask to get only images with our own deviceId -- so we don't see images from other people’s devices. You can experiment with it later and change it to call

Photo.please().giveMeDataObjectsWithCompletion { photos, error in  

so you will see all images -- if you want to see what other people were sending. :)

When images are downloaded, we assign them to a photos array and stop the activity indicator, finishing by refreshing the collection view.

Now we just need to call this function from somewhere. Add this code to your Class

//called before this view appears on the screen
override func viewWillAppear(animated: Bool) {  
   super.viewWillAppear(animated)
   //reload data before view is displayed
   self.reloadImagesFromSyncano()
}

The viewWillAppear function is called by iOS before the view appears on screen (obvious). We use this moment to start downloading images from Syncano.

Add Delegate Protocol

Before we leave this Class, there are only a few more things left to be added. Your app can now download images from Syncano and display them on a list. What we would like to do as well, is to see these images in full screen, after they are selected. To do so, add this on top of your file

protocol SelectedPhotoDelegate {  
    func userDidSelectPhoto(photo: UIImage)
}

Protocol is a way to describe a set of methods that can be called on an object. In here, our Class defines a protocol -- it will want to communicate to a delegate the moment that a user selects a photo -- but it will be up to this delegate object to decide what to do with this information.

Add somewhere inside the CollectionViewController Class a new delegate variable

var delegate : SelectedPhotoDelegate?  

It's an optional variable -- meaning this variable might or might not have a value. Our app should run ok without a delegate -- it will just mean that no action will be taken after photo is selected.

Add a method that will be called every time a user taps on an image

//add function that will be called when image was selected
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {  
   //get selected photo
   let photo = self.photos[indexPath.row]
   //if there is an image (already downloaded) pass it to delegate
   if let image = photo.downloadedImage {
      self.delegate?.userDidSelectPhoto(image)
   }
}

Now we're ready to fix the error with the activity indicator in a cell.

Add Activity Indicator to a Cell

Go back to your storyboard file. Find the Activity Indicator UI element, and drag and drop it to the middle of your cell. Set its style to Large White and color Black so it's more visible. Make sure to also select Hides When Stopped.

Add activity indicator

You should know by now how to make an outlet connection. If you don't, here’s a reminder:

Add activity indicator outlet

Run your project and make sure you no longer see any errors.

Ok, so you are downloading images from Syncano. The problem is -- there are none in there yet. We need to start sending something!

Send Images to Syncano

Go to the ViewController.swift file. You may remember that we have an empty function in there called sendPressed. So far it does nothing -- we will change it and make it save our image in Syncano.

Find the sendPressed function and change it to

//method to send current selfie image to Syncano
@IBAction func sendPressed(button: UIBarButtonItem) {
   //if there is not image, return - we don't want to send empty images
   guard let image = self.imageView.image else {
      return
   }
   let photo = Photo(name: "\(NSDate())", image: image)
   self.activityIndicator.startAnimating()
   button.enabled = false
   photo.saveWithCompletionBlock { error in
      self.activityIndicator.stopAnimating()
      button.enabled = true
      if error != nil {
         print("Error saving image: \(error)");
      }
   }
}

First, using the guard statement, we check if there is any image currently displayed. If not, the function returns -- so we don't make a connection to Syncano trying to send a non-existing image.

Then, we create a new Photo Object, set its name to the current date, and set the Photo Object image to be the image that's currently displayed on the screen.

Next, we start an activity indicator, disable the Send button, and proceed to save our photo.
When save is finished, our image is stored on Syncano, so we can stop the activity indicator and again enable the Send button.

That's almost all. Before we can run our project again, find the prepareForSegue function we added before, and change it to

//this method is called, before transition defined in storyboard is performed
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {  
   //we check the name of the transition
   if let collectionViewController = segue.destinationViewController as? CollectionViewController {
   //we add a photo to the next view controller
   collectionViewController.delegate = self
   }
}

We don't need to pass any more pictures, but we need to set ourselves as the collection view controller delegate -- so that we will know when a user presses on a picture to display it on a full screen.

Add the delegate protocol implementation at the bottom of your ViewController.swift file

// MARK: SelectedPhotoDelegate
// implementation of protocol - define what happens when user selects a photo
extension ViewController : SelectedPhotoDelegate {  
   func userDidSelectPhoto(photo: UIImage) {
      self.imageView.image = photo
      self.navigationController?.popToRootViewControllerAnimated(true)
   }
}

That's it! Now you can run your project and enjoy your selfie taker app! :)

If you have any questions or feedback, you can find me on Twitter as @lifcio. You can also just leave a comment under this post, or send an email to [email protected].

If you'd like to talk to other Syncano users like you, join us on our Slack community channel. See you there!

Build powerful apps in half the time

Use our serverless platform to set up your backend in minutes.

Learn more

#ios #developer, #kravmaga learner, dev relations at @syncano. Smash fear, learn anything!