AWS S3 Image upload using AWS SDK for iOS v2

Hi there and welcome. Today we will be talking about AWS S3 Image upload using AWS SDK for iOS v2 (WOW thats a lot of acronyms!). Last week I had to do this in a project and it took me and I was disappointed with the documentation, and sample project they had available. So in this tutorial I’m going to be showing a real world example where you would select an image from the gallery or take a photo on a device and upload that image to your bucket. If you want to follow along I have the projects available on github for both objective-c and swift! I also have a video of setting up S3 and Amazon Cognito at the end of the tutorial, but I have to apologize for the quality. I am terrible at making videos, and I was very tired when I made it (also my voice is very annoying).

Github Links

Also don’t forget to follow me on twitter if you ever want to chat about iOS or mobile development! .

Set up credentials

First thing we need to do is set up amazons credentials your app delegate’s didFinishLaunchingWithOptions method. In this demo we are going to use Cognito to manage access to our S3 bucket. If you want a full in depth look at how to do this watch the video at the end of the tutorial.

Objective-C

AppDelegate.m

//
//  AppDelegate.m
//  s3-objectiveC
//
//  Created by Barrett Breshears on 12/5/14.
//  Copyright (c) 2014 Barrett Breshears. All rights reserved.
//

#import "AppDelegate.h"
#import "AWSCore.h"

@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    AWSCognitoCredentialsProvider *credentialsProvider = [AWSCognitoCredentialsProvider
                                                          credentialsWithRegionType:AWSRegionUSEast1
                                                          accountId:@"#######"
                                                          identityPoolId:@"######"
                                                          unauthRoleArn:@"#####"
                                                          authRoleArn:@"######"];
    
    AWSServiceConfiguration *configuration = [AWSServiceConfiguration configurationWithRegion:AWSRegionUSEast1
                                                                          credentialsProvider:credentialsProvider];
    
    [AWSServiceManager defaultServiceManager].defaultServiceConfiguration = configuration;
    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application {
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
}

- (void)applicationWillTerminate:(UIApplication *)application {
}

@end

Swift

AppDelegate.swift

//
//  AppDelegate.swift
//  s3-swift
//
//  Created by Barrett Breshears on 12/6/14.
//  Copyright (c) 2014 Barrett Breshears. All rights reserved.
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        var credentialsProvider:AWSCognitoCredentialsProvider = AWSCognitoCredentialsProvider.credentialsWithRegionType(AWSRegionType.USEast1, accountId:"##########", identityPoolId:"#######", unauthRoleArn:"#######", authRoleArn:"########")
        
        var configuration:AWSServiceConfiguration = AWSServiceConfiguration(region: AWSRegionType.USEast1, credentialsProvider: credentialsProvider)
        
        AWSServiceManager.defaultServiceManager().setDefaultServiceConfiguration(configuration)
        
        
        
        return true
    }

    func applicationWillResignActive(application: UIApplication) {
    }

    func applicationDidEnterBackground(application: UIApplication) {
    }

    func applicationWillEnterForeground(application: UIApplication) {
    }

    func applicationDidBecomeActive(application: UIApplication) {
    }

    func applicationWillTerminate(application: UIApplication) {
    }


}

This is a pretty simple set up here, and if you configure your credentials in the Cognito console it will actually give you the code to set up your configuration.

Upload an image to S3

So the main difference between v1 and v2 is that you used to be able to set the upload body request to NSData or a NSURL. In v2 you can only set the upload request to a NSURL. So what we have to do is save the image to the app’s local directory and then we can create a local file url to send to amazon.

Objective-C
- (void)uploadToS3{
    // get the image from a UIImageView that is displaying the selected Image
    UIImage *img = _selectedImage.image;
    
    // create a local image that we can use to upload to s3
    NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"image.png"];
    NSData *imageData = UIImagePNGRepresentation(img);
    [imageData writeToFile:path atomically:YES];
    
    // once the image is saved we can use the path to create a local fileurl
    NSURL *url = [[NSURL alloc] initFileURLWithPath:path];
    
    // next we set up the S3 upload request manager
    _uploadRequest = [AWSS3TransferManagerUploadRequest new];
    // set the bucket
    _uploadRequest.bucket = @"s3-demo-objectivec";
    // I want this image to be public to anyone to view it so I'm setting it to Public Read
    _uploadRequest.ACL = AWSS3ObjectCannedACLPublicRead;
    // set the image's name that will be used on the s3 server. I am also creating a folder to place the image in
    _uploadRequest.key = @"foldername/image.png";
    // set the content type
    _uploadRequest.contentType = @"image/png";
    // we will track progress through an AWSNetworkingUploadProgressBlock
    _uploadRequest.body = url;
    
    __weak ViewController *weakSelf = self;
    
    _uploadRequest.uploadProgress =^(int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend){
        dispatch_sync(dispatch_get_main_queue(), ^{
            weakSelf.amountUploaded = totalBytesSent;
            weakSelf.filesize = totalBytesExpectedToSend;
            [weakSelf update];
            
        });
    };
    
    // now the upload request is set up we can creat the transfermanger, the credentials are already set up in the app delegate
    AWSS3TransferManager *transferManager = [AWSS3TransferManager defaultS3TransferManager];
    // start the upload
    [[transferManager upload:_uploadRequest] continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:^id(BFTask *task) {
        
        // once the uploadmanager finishes check if there were any errors
        if (task.error) {
            NSLog(@"%@", task.error);
        }else{// if there aren't any then the image is uploaded!
            // this is the url of the image we just uploaded
            NSLog(@"https://s3.amazonaws.com/s3-demo-objectivec/foldername/image.png");
        }
        
        return nil;
    }];
    
}
Swift
 func uploadToS3(){
        
        // get the image from a UIImageView that is displaying the selected Image
        var img:UIImage = selectedImage!.image!
        
        // create a local image that we can use to upload to s3
        var path:NSString = NSTemporaryDirectory().stringByAppendingPathComponent("image.png")
        var imageData:NSData = UIImagePNGRepresentation(img)
        imageData.writeToFile(path, atomically: true)
        
        // once the image is saved we can use the path to create a local fileurl
        var url:NSURL = NSURL(fileURLWithPath: path)!
        
        // next we set up the S3 upload request manager
        uploadRequest = AWSS3TransferManagerUploadRequest()
        // set the bucket
        uploadRequest?.bucket = "s3-demo-swift"
        // I want this image to be public to anyone to view it so I'm setting it to Public Read
        uploadRequest?.ACL = AWSS3ObjectCannedACL.PublicRead
        // set the image's name that will be used on the s3 server. I am also creating a folder to place the image in
        uploadRequest?.key = "foldername/image.png"
        // set the content type
        uploadRequest?.contentType = "image/png"
        // and finally set the body to the local file path
        uploadRequest?.body = url;
        
        // we will track progress through an AWSNetworkingUploadProgressBlock
        uploadRequest?.uploadProgress = {[unowned self](bytesSent:Int64, totalBytesSent:Int64, totalBytesExpectedToSend:Int64) in
            
            dispatch_sync(dispatch_get_main_queue(), { () -> Void in
                self.amountUploaded = totalBytesSent
                self.filesize = totalBytesExpectedToSend;
                self.update()

            })
        }
        
        // now the upload request is set up we can creat the transfermanger, the credentials are already set up in the app delegate
        var transferManager:AWSS3TransferManager = AWSS3TransferManager.defaultS3TransferManager()
        // start the upload
        transferManager.upload(uploadRequest).continueWithExecutor(BFExecutor.mainThreadExecutor(), withBlock:{ [unowned self]
            task -> AnyObject in
            
            // once the uploadmanager finishes check if there were any errors
            if(task.error != nil){
                NSLog("%@", task.error);
            }else{ // if there aren't any then the image is uploaded!
                // this is the url of the image we just uploaded
                NSLog("https://s3.amazonaws.com/s3-demo-swift/foldername/image.png");
            }
            
            self.removeLoadingView()
            return "all done";
        })
        
    }

Once you understand how to configure your credentials and how to get the image from an image view to local file to url to amazon the process is pretty simple. Please let me know if you have any questions in the comments below or on send me a tweet
.

Setting up credentials and project walk through video

Author: barrettbreshears

I don't take showers, only blood baths.

20 thoughts on “AWS S3 Image upload using AWS SDK for iOS v2”

  1. Hey Barrett, Great tutorial – I wish I had run across it before I spent a couple of days figuring out how to do a lot of what you showed.

    I do have a question. Do you know how often the uploadProgress block is called and if that can be changed? I can’t find documentation on it.

    Cheers and Happy New Year,
    Jerry

    1. Hi Jerry,
      Glad you enjoyed the tutorial. I couldn’t find anything on what determined the uploadProgress block to be called. It would be pretty simple to figure out how much data was sent between the block calls to determine the packet size being uploaded. I looked through the documentation and found some stuff on setting the packet size for v1 but couldn’t find anything on v2. I will try opening a question on their developer forum and see if they can shed any light on this subject. I will let you know if they provide any useful information.

      Barrett

  2. Dear Barrett,

    Thank you very much for the tutorial. I looked on the internet for a long time and finally found your post and video. They are really helpful!

    I tried to follow your code as close as I could and here is my code below. Somehow I got the error message of “timed out”.

    2015-01-02 20:25:44.815 test_picupload[1847:693b] TMDiskCache.m (205) ERROR: The operation couldn’t be completed. (Cocoa error 4.) Can you think of anything that could cause this?

    2015-01-02 20:25:44.818 test_picupload[1847:60b] Error: [Error Domain=NSURLErrorDomain Code=-1001 “The request timed out.” UserInfo=0x146bd030 {NSErrorFailingURLStringKey=https://s3.amazonaws.com/picprofilechat/servimage.png, NSErrorFailingURLKey=https://s3.amazonaws.com/picprofilechat/servimage.png, NSLocalizedDescription=The request timed out., NSUnderlyingError=0x146af9f0 “The request timed out.”}]
    2015-01-02 20:25:44.819 test_picupload[1847:60b] upload executed

    Thanks,
    Di

    – (void)dispatchOperations {

    NSLog(@”dispatchOperations”);
    UIImage *img = self.imagetouploadview.image;
    NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@”image.png”];
    NSData *imageData = UIImagePNGRepresentation(img);
    [imageData writeToFile:path atomically:YES];
    NSURL *url = [[NSURL alloc] initFileURLWithPath:path];

    self.uploadRequest = [AWSS3TransferManagerUploadRequest new];
    self.uploadRequest.bucket = S3BucketName;
    self.uploadRequest.key = @”servimage.png”;
    self.uploadRequest.contentType= @”image/png”;
    self.uploadRequest.body = url;
    NSLog(@”URLdone”);

    // __weak ViewController *weakSelf = self;
    //
    // _uploadRequest.uploadProgress =^(int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend){
    // dispatch_sync(dispatch_get_main_queue(), ^{
    // weakSelf.amountUploaded = totalBytesSent;
    // weakSelf.filesize = totalBytesExpectedToSend;
    // [weakSelf update];
    //
    // });
    // };
    AWSS3TransferManager *transferManager = [AWSS3TransferManager defaultS3TransferManager];

    [AWSLogger defaultLogger].logLevel = AWSLogLevelVerbose;
    [[transferManager upload:self.uploadRequest] continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:^id(BFTask *task) {
    if (task.error != nil) {
    NSLog(@”Error: [%@]”, task.error);
    // self.uploadStatusLabel.text = StatusLabelFailed;
    // AWS SDK for iOS 2.0 Developer Guide Uploading a File
    } else {
    self.uploadRequest = nil;
    }
    NSLog(@”upload executed”);
    return nil;
    }];
    }

    1. The code looks correct I think you might have an issue with the credentials in your app delegate. Make sure your region is correct in you s3 console and it matches your configuration in your app delegate set up. My skype username is barrett.breshears or Twitter user name is barrettbreshear send me a message if that’s not the issue and you want some help! Thanks for the comment!

  3. Dear Barrett,

    The “timed out” error was fixed after I recreated a bucket in S3. However I found an interesting phenomenon: I was able to see the picture in my S3 bucket but in the log I get this error “2015-01-03 00:08:49.690 test_picupload[1922:1d07] TMDiskCache.m (205) ERROR: The operation couldn’t be completed. (Cocoa error 4.)
    2015-01-03 00:08:49.692 test_picupload[1922:60b] “.

    This error is only reported because I added this line “[AWSLogger defaultLogger].logLevel = AWSLogLevelVerbose;” Otherwise there is no error.

    Thanks,
    Dillon

  4. Very interesting tutorial, I can’t wait to work through the steps on my own. I’ve been looking to do something similar with video files from the users Library, so I did have one question. Is it required to re-save the file to the apps local directory before uploading? I’m guessing with larger videos this could be in issue with storage space on the device which is why I’m asking. Thanks,

    Tim

    1. Hi Tim! Thanks for the comment. If you are doing video I’m pretty sure you can use

      - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
          NSLog(@"%@", info);
      
          // An image
          if([info[UIImagePickerControllerMediaType] isEqualToString:@"public.image"])
              UIImage *image=info[UIImagePickerControllerOriginalImage];
      
          // A video
          else
              NSURL *url=info[UIImagePickerControllerMediaURL]];
      }
  5. Barret thank you for posting this. I completely agree that AWS does a poor job documenting their AWS SDK. Will this tutorial work with iOS7? Or is it only compatible with iOS8?

  6. I wanted to say a huge thanks for showing the AWS console and how to set up things there. I have been trying to understand SDK examples with the console set up and there were just some things I could not understand. Seeing someone in a video set it up is fantastic. Thank you.

  7. If I just download the project, run “pod install”, and open the workspace, I get about 20 errors. They are about redefinition of almost all the methods in “aws SDK”. Also, the “credentialsProvider” part in AppDelegate.m is different on Amazon website and in your tutorial. I tried opening the workspace (not xcode project file) directly too, without doing pod install, it doesn’t help.

    1. Hi Parag,
      I will take a look at the project and let you know if I find any issues. I have had a couple people run the project without any issues but there could have been a major update with the sdk. I have your email and will let you know what I find. Thanks for the comment!

      Barrett

    2. Hi Parag,
      I just cloned the project to my computer and ran the project. It seems to be working great. If you want I can jump on a skype call with you and see what the problem is. My skype username is barrett.breshears .

  8. Hey the method AWSCognitoCredentialsProvider.credentialsWithRegionType(AWSRegionType.USEast1, accountId:”##########”, identityPoolId:”#######”, unauthRoleArn:”#######”, authRoleArn:”########”) is throwing error saying its not available.

    Could you please help me on this

  9. Thanks for the tutorial!

    It would be really great if you could give a tutorial on how to pull the image down from S3 or how to sync it with DynamoDB to store the URL to make the request to! Trying to figure out how to use S3 and DynamoDB for a photo sharing application. Thanks again!!

Comments are closed.