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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Alamofire Tutorial: Getting Started
25 mins
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:
- Hide the upload button, and show the progress view and activity view.
- While the file uploads, you call the progress handler with an updated percent. This updates the progress indicator of the progress bar.
- The completion handler executes when the upload finishes. This sets the controls back to their original state.
- 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:
- The image that’s being uploaded needs to be converted to a
Data
instance. - Here you convert the JPEG data blob (
imageData
) into a MIME multipart request to send to the Imagga content endpoint.
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:
- Check that the upload was successful, and the result has a value; if not, print the error and call the completion handler.
- Using SwiftyJSON, retrieve the
firstFileID
from the response. - 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:
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:
- 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 replaceBasic xxx
with your actual authorization header. - Check that the response was successful, and the result has a value; if not, print the error and call the completion handler.
- Using SwiftyJSON, retrieve the raw
tags
array from the response. Iterate over each dictionary object in thetags
array, retrieving the value associated with thetag
key. - 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:
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:
- 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 replaceBasic xxx
with your actual authorization header. - Check that the response was successful, and the result has a value; if not, print the error and call the completion handler.
- Using SwiftyJSON, retrieve the
image_colors
array from the response. Iterate over each dictionary object in theimage_colors
array, and transform it into aPhotoColor
object. This object pairs colors in the RGB format with the color name as a string. - 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:
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.