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! Follow @BarrettBreshear.
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 Follow @BarrettBreshear
.
Jerry Howard
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
barrettbreshears
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
Dillon Hu
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;
}];
}
barrettbreshears
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!
Dillon Hu
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
Tim S
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
barrettbreshears
Hi Tim! Thanks for the comment. If you are doing video I’m pretty sure you can use
Red
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?
barrettbreshears
Thanks so much for the comment and reading the post! This should be compatible with iOS7
Maxim
Hi,
Thanks for video, i have a leak issue when i upload to AWS
http://stackoverflow.com/questions/29226674/how-do-i-resolve-my-leak-issue
Can you please review ?
Thanks
barrettbreshears
Hi thanks, for the comment I will take a look.
David
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.
barrettbreshears
Hi Thanks so much for the comment. Glad it helped!
Parag
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.
barrettbreshears
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
barrettbreshears
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 .
Girish
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
barrettbreshears
They probably updated the sdk and his method might not be available anymore. I will take a look when a get a chance, and let you know if I find anything.
Steven
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!!
barrettbreshears
Thanks, that’s a great idea. I will try to get to something like that soon. Thanks so much for the feedback.