Cloud Storage with AWS S3 – Part 2: Using the SDK

In the previous blog we covered an introduction to S3 storage, created an S3 bucket and did a simple download using NSURLSession. The issue with that is that the image file had to have public access. This means that anyone with the URL would be able to download the contents of the bucket. In reality it’s not a great idea to have such open access. In order to have control over what users can do to your S3 buckets it’s necessary to grasp a few other exciting AWS services. These being; Cognito and IAM. As well as this we’ll cover using CocoaPods to install the AWS SDK. If you’re thinking: that sounds like a hot tamale! Don’t worry, we’ll break it into smaller, bit size tamales.

Installing CocoaPods

In order for your app to best interact with AWS you’re going to need to install the SDK (software development kit) that they’ve made for iOS/Swift. A great way to manage these frameworks (namely the AWSS3 and AWSCore) is with CocoaPods.

CocoaPods is a dependency manager which organizes a wide world of third party libraries.  With a few short commands CocoaPods will fetch the latest version of the frameworks as well as setting up the Xcode workspace necessary to get started.

1)  Don a trench coat and sunglasses because we’re going into the Matrix. OPEN THE TERMINAL WINDOW!

2)  Make sure the text in your terminal window is green (optional)

3) Enter:

sudo gem install cocoapods

4)  Type in your password.

5)   Enter:

pod setup --verbose

If all’s gone well CocoaPods is now installed.  One issue that may come up is if you have an older version of Ruby installed. CocoaPods is built using Ruby, which comes with OSX. If the terminal can’t install CocoaPods because of this here’s how you update Ruby:

SDKS3_Ruby

So the command to run is:

rvm install 2.2
rvm use 2.2

If on the off chance you don’t have the RVM (Ruby Version Manager) run this command:

\curl -sSL https://get.rvm.io | bash

We’ll return to CocoaPod-land later…

Configure Credentials with Cognito

Cognito is an Amazon service for mobile apps that can save you a lot of work! It can easily be set up to handle user sign in / authentication,  as well as to control user access to your AWS services. It can also be used to save user data for sync across multiple devices.

We’re going to use it in it’s most basic form: to allow unauthenticated users to gain access to an S3 bucket and be able to upload + download to that S3 bucket. This means that users don’t have a sign in screen. We’re simply using Cognito to grant our app granular control to S3, while keeping S3 locked to the outside world.

1)  In the AWS console go to Cognito (Magnito’s lesser known step-brother)

2)  Click “Manage Federated Identities”

3)  Click “Create New Identity Pool”

4)  Give the new pool a name (e.g. the name of you app)

5)  Select “Enable access to unauthenticated identities” and hit “Create Pool”

SDKS3_NamePool

 

6)  Click “Allow”.  It’s going to create new IAM roles for authenticated and unauthenticated users. We’ll tweak those later.

7)  Select the iOS developer kit.

8)  Copy the code that looks similar to this. It contains the region that the identity pool is in as well as the unique identity pool ID.

// Initialize the Amazon Cognito credentials provider

let credentialsProvider = AWSCognitoCredentialsProvider(regionType:.USWest2,
   identityPoolId:"#YOUR_IDENTITY_POOLID#")

let configuration = AWSServiceConfiguration(region:.USWest2, credentialsProvider:credentialsProvider)

AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration

Create an S3 Bucket

Create a new S3 bucket, upload a jpg image and set the permissions set to private (default). Please see last week’s blog for how to set up and S3 bucket. Copy down the name of the bucket.

Configure the IAM Role

The AWS service IAM stands for Identity and Access Management. We’re going to use it to configure a “role”. An AWS role can be assigned to a resource (in this can Cognito) to give it specific access to other AWS resources (in this case S3). This is done by configuring the policies that are attached to the role.

1)  Go to the IAM service in the AWS console.

2) Click on the “Roles” button on the left side.

3)  Find the role you just created for unauthenticated users and click on it.

4)  Click “Create Role Policy”

5)  Click “Select” on the “Policy Generator”

6)  Choose the AWS Service: Amazon S3

7)  Actions: “GetObject” and “PutObject”

8)  Add your bucket name in this format: arn:aws:s3:::yourBucketName

9) Click “Add Statement”

SDKS3_RolePermissions

    10)  Click “Next Step” and click “Apply Policy”

Don’t worry we’re not too far away from busting out some Swift code I promise!

Create a new Xcode Project

Create a new single view application project in Xcode.

Using CocoaPods to Install the AWS SDK

Now for the Cocoapod magic! Hopefully you’re still wearing the trench coat and sunglasses because we’re going back to the terminal.

It means fasten your seat belt Dorothy, ’cause Kansas is going bye-bye. – Cypher

1)  Open the terminal and navigate to the directory you just made for the Xcode project. For example:

cd Documents/XcodeProjects/S3DemoApp

2)  Enter:

pod init

This creates the Podfile that we’ll use to configure our dependencies.

3)  Enter:

open -a Xcode Podfile

This opens the Podfile in Xcode.

4) Change the Podfile to this. Where “target” is the exact name of your Xcode project!

# Podfile
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
use_frameworks!

target 'BlogS3WithSDK' do
  pod 'AWSS3'

end

It’s very important that the platform you choose here matches the deployment target you have in Xcode under AppIcon > Gereral > Deployment Info >  Deployment Target

5)  Close Xcode

6) In the terminal enter:

pod install

So now if you look in the project folder you’ll see that not only are the frameworks installed but there’s also an Xcode workspace file. This workspace file includes the project file as well as the AWS frameworks. From now on it’s very important that you work using the workspace file (not just the project file).

SDKS3_Folder

7)  Open the xcwordspace

8)  Navigate to the main ViewController and under “import UIKit” add “import AWSS3”. You may find that Xcode gives you an error saying “no such module…”. To fix this go to Menu > Product > Clean . Then Menu > Product > Run . + you should be good to go.

Let’s Get Swifting!

OK, it’s quite a lot of code. Copy or type it into the ViewController file. We’ll break it down into 3 parts. Follow along with the number annotations below the code.

import UIKit
import AWSS3

class ViewController: UIViewController {
    
    // 1S)
    // AWS constants
    let S3BucketName = "YOUR_BUCKET_NAME"
    let region = AWSRegionType.USWest2
    let identityPool = "YOUR_IDENTITY_POOL_NAME"
    
    // image view for download image
    var imageView: UIImageView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 2S)
        // Initialize the Amazon Cognito credentials provider
        let credentialsProvider = AWSCognitoCredentialsProvider(regionType: region,
                                                                identityPoolId: identityPool)
        
        let configuration = AWSServiceConfiguration(region: region, credentialsProvider:credentialsProvider)
        
        AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration

        // 3S)
        // add button for upload
        let uploadBtn = UIButton(frame: CGRect(x: 100, y: 50, width: 200, height: 50))
        uploadBtn.backgroundColor = .blueColor()
        uploadBtn.setTitle("Upload Image", forState: .Normal)
        uploadBtn.addTarget(self, action: #selector(uploadBtnAction), forControlEvents: .TouchUpInside)
        self.view.addSubview(uploadBtn)
        
        // add button for download
        let downloadBtn = UIButton(frame: CGRect(x: 100, y: 120, width: 200, height: 50))
        downloadBtn.backgroundColor = .blueColor()
        downloadBtn.setTitle("Download Image", forState: .Normal)
        downloadBtn.addTarget(self, action: #selector(downloadBtnAction), forControlEvents: .TouchUpInside)
        self.view.addSubview(downloadBtn)
        
        // initialize image view for downloaded image
        let ivFrame = CGRect(x: 0, y: 250, width: 300, height: 200)
        imageView = UIImageView(frame: ivFrame)
        
    }
    
    func uploadBtnAction(sender: UIButton!) {
        uploadFile()
    }
    
    func downloadBtnAction(sender: UIButton!) {
        downloadFile()
    }
    
    func uploadFile() {
        
        // 1U)
        let ext = "jpg"
        let imageURL = NSBundle.mainBundle().URLForResource("YOUR_IMAGE_NAME", withExtension: ext)!
        
        // 2U)
        let uploadRequest = AWSS3TransferManagerUploadRequest()
        uploadRequest.bucket = S3BucketName
        uploadRequest.key = NSProcessInfo.processInfo().globallyUniqueString + "." + ext
        uploadRequest.body = imageURL
        uploadRequest.contentType = "image/" + ext
        
        // 3U)
        let transferManager = AWSS3TransferManager.defaultS3TransferManager()
        transferManager.upload(uploadRequest).continueWithBlock { (task) -> AnyObject! in
            if let error = task.error {
                print("uploadFile() failed: (\(error))")
            }
            if let exception = task.exception {
                print("uploadFile() failed: (\(exception))")
            }
            if task.result != nil {
                let s3URL = NSURL(string: "http://s3.amazonaws.com/\(self.S3BucketName)/\(uploadRequest.key!)")!
                print("uploadFile() successful to: \(s3URL)")
            }
            else {
                print("Unexpected empty result.")
            }
            return nil
        }
    }
    
    func downloadFile() {
        // 1D)
        let imageName = "YOUR_DOWNLOAD_IMAGE_NAME.jpg"
        let downloadedFilePath = NSTemporaryDirectory().stringByAppendingString("downloaded-\(imageName)")
        let downloadedFileURL = NSURL(fileURLWithPath: downloadedFilePath)
        
        // 2D)
        let downloadRequest = AWSS3TransferManagerDownloadRequest()
        downloadRequest.bucket = S3BucketName
        downloadRequest.key = imageName
        downloadRequest.downloadingFileURL = downloadedFileURL
        
        // 3D)
        let transferManager = AWSS3TransferManager.defaultS3TransferManager()
        transferManager.download(downloadRequest).continueWithBlock { (task) -> AnyObject! in
            if let error = task.error {
                print("downloadFile() failed: (\(error))")
            }
            else if let exception = task.exception {
                print("downloadFile() failed: (\(exception))")
            }
            else if task.result != nil {
                
                if let data = NSData(contentsOfURL: downloadedFileURL) {
                    // update the UI on the main queue
                    dispatch_async(dispatch_get_main_queue()) {
                        self.imageView!.image = UIImage(data: data)
                        self.imageView!.contentMode = UIViewContentMode.ScaleAspectFit
                        self.view.addSubview(self.imageView!)
                    }
                }
            }
            else {
                print("Unexpected empty result")
            }
            return nil
        }
    }
}

The Setup

Firstly, find a jpg image that you can use to test uploading with. Drag it from the finder onto the Xcode Project Navigator window on the left side of Xcode. Make sure to place it in the folder for your project (not the Pods project folder).  Check “copy items if needed”.

(Follow the annotation numbers in the code above)

1S)  Here’s where we’ve defined the constant that the SDK code will use. It’s a nice idea, particularly on larger projects to keep this in a separate Swift file.

2S) Initialize the credentials provider with the identity pool we created and configure the AWS service manager.

3S) Programmatically create buttons for uploading and downloading along with a UIImageView to hold the downloaded image.

To Upload an Image to S3

1U) Construct an NSURL for the locally stored image

2U) Construct a request object

3U) Create an instance of the S3 transfer manager and pass in the request

The .key value represents the name of the object and the .body is an NSURL. In this example we’ve generated a unique string to identify the object when it’s put on S3. You could just as easily give it the same name as the original file by setting the key:

uploadRequest.key = imageURL.lastPathComponent

Note: the transfer manager upload request is asynchronous, so it’s ok to have it in the main thread (it wont freeze the UI).

More details on using the transfer manager can be found from AWS here (unfortunately as of right now it’s written in Objective-C).

To Download an Image From S3

Downloading follows a similar pattern

1D) Construct a file path for the download

2D) Construct a download request

3D) Create an instance of the S3 transfer manager and pass in the request

Conclusion

There are a few different ways of working with the Amazon SDK. Here we’ve looked at added the SDK with CocoaPods and manually adding the Swift code. There’s also an amazing AWS service called Mobile Hub which can customize and provision services for your app and also generate a sample project with code examples. It’s pretty incredible and worth checking out once you’re familiar with some of the basics of the SDK.

Another great reference for using S3 is the official S3TransferManager demo from AWS. Check that out here.

You can download the whole workspace from this blog on GitHub here (just add in your own credentials for AWS).

If you’ve found the post helpful please share with the links below!

For the latest updates + blog posts follow me on twitter here.

Leave a Reply