Alamofire Tutorial: Getting Started

Take your first steps into Alamofire, the de facto networking library on iOS powering thousands of apps, by using the Imagga APIs to upload and analyze user photos. By Ron Kliffer.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Uploading Files

Open ViewController.swift and add the following to the top, below import SwiftyJSON:

import Alamofire

This lets you use the functionality provided by the Alamofire module in your code, which you’ll be doing soon!

Next, go to imagePickerController(_:didFinishPickingMediaWithInfo:) and add the following to the end, right before the call to dismiss(animated:):

// 1
takePictureButton.isHidden = true
progressView.progress = 0.0
progressView.isHidden = false
activityIndicatorView.startAnimating()

upload(image: image,
       progressCompletion: { [weak self] percent in
        // 2
        self?.progressView.setProgress(percent, animated: true)
  },
       completion: { [weak self] tags, colors in
        // 3
        self?.takePictureButton.isHidden = false
        self?.progressView.isHidden = true
        self?.activityIndicatorView.stopAnimating()
            
        self?.tags = tags
        self?.colors = colors
            
        // 4
        self?.performSegue(withIdentifier: "ShowResults", sender: self)
})

Everything with Alamofire is asynchronous, which means you’ll update the UI in an asynchronous manner:

  1. Hide the upload button, and show the progress view and activity view.
  2. While the file uploads, you call the progress handler with an updated percent. This updates the progress indicator of the progress bar.
  3. The completion handler executes when the upload finishes. This sets the controls back to their original state.
  4. Finally the Storyboard advances to the results screen when the upload completes, successfully or not. The user interface doesn’t change based on the error condition.

Next, find upload(image:progressCompletion:completion:) at the bottom of the file. It is currently only a method stub, so give it the following implementation:

func upload(image: UIImage,
            progressCompletion: @escaping (_ percent: Float) -> Void,
            completion: @escaping (_ tags: [String]?, _ colors: [PhotoColor]?) -> Void) {
  // 1
  guard let imageData = UIImageJPEGRepresentation(image, 0.5) else {
    print("Could not get JPEG representation of UIImage")
    return
  }

  // 2
  Alamofire.upload(multipartFormData: { multipartFormData in
    multipartFormData.append(imageData,
                             withName: "imagefile",
                             fileName: "image.jpg",
                             mimeType: "image/jpeg")
  },
                   to: "http://api.imagga.com/v1/content",
                   headers: ["Authorization": "Basic xxx"],
                   encodingCompletion: { encodingResult in
  })
}

Here’s what’s happening:

  1. The image that’s being uploaded needs to be converted to a Data instance.
  2. Here you convert the JPEG data blob (imageData) into a MIME multipart request to send to the Imagga content endpoint.
Note: Make sure to replace Basic xxx with the actual authorization header taken from the Imagga dashboard.

Next, add the following to the encodingCompletion closure:

switch encodingResult {
case .success(let upload, _, _):
  upload.uploadProgress { progress in
    progressCompletion(Float(progress.fractionCompleted))
  }
  upload.validate()
  upload.responseJSON { response in
  }
case .failure(let encodingError):
  print(encodingError)
}

This chunk of code calls the Alamofire upload function and passes in a small calculation to update the progress bar as the file uploads. It then validates the response has a status code in the default acceptable range between 200 and 299.

Note: Prior to Alamofire 4 it was not guaranteed progress callbacks were called on the main queue. Beginning with Alamofire 4, the new progress callback API is always called on the main queue.

Note: Prior to Alamofire 4 it was not guaranteed progress callbacks were called on the main queue. Beginning with Alamofire 4, the new progress callback API is always called on the main queue.

Next, add the following code to the upload.responseJSON closure:

// 1
guard response.result.isSuccess,
  let value = response.result.value else {
    print("Error while uploading file: \(String(describing: response.result.error))")
    completion(nil, nil)
    return
}
                        
// 2
let firstFileID = JSON(value)["uploaded"][0]["id"].stringValue
print("Content uploaded with ID: \(firstFileID)")
                        
//3
completion(nil, nil)

Here’s a step-by-step explanation of the above code:

  1. Check that the upload was successful, and the result has a value; if not, print the error and call the completion handler.
  2. Using SwiftyJSON, retrieve the firstFileID from the response.
  3. Call the completion handler to update the UI. At this point, you don’t have any downloaded tags or colors, so simply call this with no data.

Note: Every response has a Result enum with a value and type. Using automatic validation, the result is considered a success when it returns a valid HTTP Code between 200 and 299 and the Content Type is of a valid type specified in the Accept HTTP header field.

You can perform manual validation by adding .validate options as shown below:

Note: Every response has a Result enum with a value and type. Using automatic validation, the result is considered a success when it returns a valid HTTP Code between 200 and 299 and the Content Type is of a valid type specified in the Accept HTTP header field.

You can perform manual validation by adding .validate options as shown below:

Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"])
  .validate(statusCode: 200..<300)
  .validate(contentType: ["application/json"])
  .response { response in
  // response handling code
}
Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"])
  .validate(statusCode: 200..<300)
  .validate(contentType: ["application/json"])
  .response { response in
  // response handling code
}

The UI won't show an error if you hit an error during the upload; it merely returns no tags or colors to the user. This isn't the best user experience, but it's fine for this tutorial.

Build and run your project; select an image and watch the progress bar change as the file uploads. You should see a note like the following in your console when the upload completes:

ImaggaUploadConsole

Congratulations, you've successfully uploaded a file over the Interwebs!

Retrieving Data

The next step after uploading the image to Imagga is to fetch the tags Imagga produces after it analyzes the photo.

Add the following method to the ViewController extension below upload(image:progress:completion:):

func downloadTags(contentID: String, completion: @escaping ([String]?) -> Void) {
  // 1
  Alamofire.request("http://api.imagga.com/v1/tagging",
                    parameters: ["content": contentID],
                    headers: ["Authorization": "Basic xxx"])
     // 2
    .responseJSON { response in
      guard response.result.isSuccess,
        let value = response.result.value else {
          print("Error while fetching tags: \(String(describing: response.result.error))")
          completion(nil)
          return
      }
      
      // 3
      let tags = JSON(value)["results"][0]["tags"].array?.map { json in
        json["tag"].stringValue
      }
        
      // 4
      completion(tags)
  }
}

Here's a step-by-step explanation of the above code:

  1. Perform an HTTP GET request against the tagging endpoint, sending the URL parameter content with the ID you received after the upload. Again, be sure to replace Basic xxx with your actual authorization header.
  2. Check that the response was successful, and the result has a value; if not, print the error and call the completion handler.
  3. Using SwiftyJSON, retrieve the raw tags array from the response. Iterate over each dictionary object in the tags array, retrieving the value associated with the tag key.
  4. Call the completion handler passing in the tags received from the service.

Next, go back to upload(image:progress:completion:) and replace the call to the completion handler in the success condition with the following:

self.downloadTags(contentID: firstFileID) { tags in
  completion(tags, nil)
}

This simply sends along the tags to the completion handler.

Build and run your project; select a photo and you should see something similar to the following appear:

alamofire tutorial

Pretty slick! That Imagga is one smart API. :] Next, you'll fetch the colors of the image.

Add the following method to the ViewController extension below downloadTags(contentID:completion:):

func downloadColors(contentID: String, completion: @escaping ([PhotoColor]?) -> Void) {
  // 1.
  Alamofire.request("http://api.imagga.com/v1/colors",
                    parameters: ["content": contentID],
                    headers: ["Authorization": "Basic xxx"])
    .responseJSON { response in
      // 2
      guard response.result.isSuccess,
        let value = response.result.value else {
          print("Error while fetching colors: \(String(describing: response.result.error))")
          completion(nil)
          return
      }
        
      // 3
      let photoColors = JSON(value)["results"][0]["info"]["image_colors"].array?.map { json in
        PhotoColor(red: json["r"].intValue,
                   green: json["g"].intValue,
                   blue: json["b"].intValue,
                   colorName: json["closest_palette_color"].stringValue)
      }
        
      // 4
      completion(photoColors)
  }
}

Taking each numbered comment in turn:

  1. Perform an HTTP GET request against the colors endpoint, sending the URL parameter content with the ID you received after the upload. Again, be sure to replace Basic xxx with your actual authorization header.
  2. Check that the response was successful, and the result has a value; if not, print the error and call the completion handler.
  3. Using SwiftyJSON, retrieve the image_colors array from the response. Iterate over each dictionary object in the image_colors array, and transform it into a PhotoColor object. This object pairs colors in the RGB format with the color name as a string.
  4. Call the completion handler, passing in the photoColors from the service.

Finally, go back to upload(image:progress:completion:) and replace the call to downloadTags(contentID:) in the success condition with the following:

self.downloadTags(contentID: firstFileID) { tags in
  self.downloadColors(contentID: firstFileID) { colors in
    completion(tags, colors)
  }
}

This nests the operations of uploading the image, downloading tags and downloading colors.

Build and run your project again; this time, you should see the returned color tags when you select the Colors button:

alamofire tutorial

This uses the RGB colors you mapped to PhotoColor structs to change the background color of the view. You've now successfully uploaded an image to Imagga and fetched data from two different endpoints. You've come a long way, but there's some room for improvement in how you're using Alamofire in PhotoTagger.