Handle Multiple File (Image) Uploads to Aws S3 Swift

Efficient way to upload multiple images to S3 from iOS

As I said on H. Al-Amri's response, if you need to know when the last upload is complete you can't simply iterate through an array of data and upload them all at once.

In Javascript there is a library (Async.js I think) that will make it easy to do background operations on individual elements of an array, and to get callbacks when each one finishes and when the entire array is finished. Since I'm not aware of anything like that for Swift, your intuition that you have to chain the uploads is correct.

As @DavidTamrazov explained in comments, you can chain your calls together using recursion. The way I solved this problem was a little more complicated since my network operations are done using NSOperationQueue to chain NSOperations. I pass an array of images to a custom NSOperation that uploads the first image from the array. When it completes, it adds another NSOperation to my NSOperationsQueue with the array of remaining images. When the array runs out of images, I know I'm complete.

Following is an example I chopped out of a much much larger chunk I use. Treat it as pseudocode because I edited it heavily and didn't have time to even compile it. But hopefully it's clear enough on the framework for how to do this with NSOperations.

class NetworkOp : Operation {
var isRunning = false

override var isAsynchronous: Bool {
get {
return true
}
}

override var isConcurrent: Bool {
get {
return true
}
}

override var isExecuting: Bool {
get {
return isRunning
}
}

override var isFinished: Bool {
get {
return !isRunning
}
}

override func start() {
if self.checkCancel() {
return
}
self.willChangeValue(forKey: "isExecuting")
self.isRunning = true
self.didChangeValue(forKey: "isExecuting")
main()
}

func complete() {
self.willChangeValue(forKey: "isFinished")
self.willChangeValue(forKey: "isExecuting")
self.isRunning = false
self.didChangeValue(forKey: "isFinished")
self.didChangeValue(forKey: "isExecuting")
print( "Completed net op: \(self.className)")
}

// Always resubmit if we get canceled before completion
func checkCancel() -> Bool {
if self.isCancelled {
self.retry()
self.complete()
}
return self.isCancelled
}

func retry() {
// Create a new NetworkOp to match and resubmit since we can't reuse existing.
}

func success() {
// Success means reset delay
NetOpsQueueMgr.shared.resetRetryIncrement()
}
}

class ImagesUploadOp : NetworkOp {
var imageList : [PhotoFileListMap]

init(imageList : [UIImage]) {
self.imageList = imageList
}

override func main() {
print( "Photos upload starting")
if self.checkCancel() {
return
}

// Pop image off front of array
let image = imageList.remove(at: 0)

// Now call function that uses AWS to upload image, mine does save to file first, then passes
// an error message on completion if it failed, nil if it succceeded
ServerMgr.shared.uploadImage(image: image, completion: { errorMessage ) in
if let error = errorMessage {
print("Failed to upload file - " + error)
self.retry()
} else {
print("Uploaded file")
if !self.isCancelled {
if self.imageList.count == 0 {
// All images done, here you could call a final completion handler or somthing.
} else {
// More images left to do, let's put another Operation on the barbie:)
NetOpsQueueMgr.shared.submitOp(netOp: ImagesUploadOp(imageList: self.imageList))
}
}
}
self.complete()
})
}

override func retry() {
NetOpsQueueMgr.shared.retryOpWithDelay(op: ImagesUploadOp(form: self.form, imageList: self.imageList))
}
}

// MARK: NetOpsQueueMgr -------------------------------------------------------------------------------

class NetOpsQueueMgr {
static let shared = NetOpsQueueMgr()

lazy var opsQueue :OperationQueue = {
var queue = OperationQueue()
queue.name = "myQueName"
queue.maxConcurrentOperationCount = 1
return queue
}()

func submitOp(netOp : NetworkOp) {
opsQueue.addOperation(netOp)
}

func uploadImages(imageList : [UIImage]) {
let imagesOp = ImagesUploadOp(form: form, imageList: imageList)
self.submitOp(netOp: imagesOp)
}
}

Swift - AWS S3 Upload Image from Photo Library and download it

After doing many research I've got this working,

import AWSS3
import AWSCore

Upload:

I assume you have implemented UIImagePickerControllerDelegate already.

Step 1:

  • Create variable for holding url:

    var imageURL = NSURL()
  • Create upload completion handler obj:

    var uploadCompletionHandler: AWSS3TransferUtilityUploadCompletionHandlerBlock?

Step 2: Get Image URL from imagePickerController(_:didFinishPickingMediaWithInfo:):

  • Set value of imageURL in this delegate method:

    func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]){

    //getting details of image
    let uploadFileURL = info[UIImagePickerControllerReferenceURL] as! NSURL

    let imageName = uploadFileURL.lastPathComponent
    let documentDirectory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first! as String

    // getting local path
    let localPath = (documentDirectory as NSString).stringByAppendingPathComponent(imageName!)

    //getting actual image
    let image = info[UIImagePickerControllerOriginalImage] as! UIImage
    let data = UIImagePNGRepresentation(image)
    data!.writeToFile(localPath, atomically: true)

    let imageData = NSData(contentsOfFile: localPath)!
    imageURL = NSURL(fileURLWithPath: localPath)

    picker.dismissViewControllerAnimated(true, completion: nil)
    }

Step 3: Call this uploadImage method after imageURL set to Upload Image to your bucket:

func uploadImage(){

//defining bucket and upload file name
let S3BucketName: String = "bucketName"
let S3UploadKeyName: String = "public/testImage.jpg"

let expression = AWSS3TransferUtilityUploadExpression()
expression.uploadProgress = {(task: AWSS3TransferUtilityTask, bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) in
dispatch_async(dispatch_get_main_queue(), {
let progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
print("Progress is: \(progress)")
})
}

self.uploadCompletionHandler = { (task, error) -> Void in
dispatch_async(dispatch_get_main_queue(), {
if ((error) != nil){
print("Failed with error")
print("Error: \(error!)");
}
else{
print("Sucess")
}
})
}

let transferUtility = AWSS3TransferUtility.defaultS3TransferUtility()

transferUtility.uploadFile(imageURL, bucket: S3BucketName, key: S3UploadKeyName, contentType: "image/jpeg", expression: expression, completionHander: uploadCompletionHandler).continueWithBlock { (task) -> AnyObject! in
if let error = task.error {
print("Error: \(error.localizedDescription)")
}
if let exception = task.exception {
print("Exception: \(exception.description)")
}
if let _ = task.result {
print("Upload Starting!")
}

return nil;
}
}

Download:

func downloadImage(){

var completionHandler: AWSS3TransferUtilityDownloadCompletionHandlerBlock?

let S3BucketName: String = "bucketName"
let S3DownloadKeyName: String = "public/testImage.jpg"

let expression = AWSS3TransferUtilityDownloadExpression()
expression.downloadProgress = {(task: AWSS3TransferUtilityTask, bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) in
dispatch_async(dispatch_get_main_queue(), {
let progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
print("Progress is: \(progress)")
})
}

completionHandler = { (task, location, data, error) -> Void in
dispatch_async(dispatch_get_main_queue(), {
if ((error) != nil){
print("Failed with error")
print("Error: \(error!)")
}
else{
//Set your image
var downloadedImage = UIImage(data: data!)
}
})
}

let transferUtility = AWSS3TransferUtility.defaultS3TransferUtility()

transferUtility.downloadToURL(nil, bucket: S3BucketName, key: S3DownloadKeyName, expression: expression, completionHander: completionHandler).continueWithBlock { (task) -> AnyObject! in
if let error = task.error {
print("Error: \(error.localizedDescription)")
}
if let exception = task.exception {
print("Exception: \(exception.description)")
}
if let _ = task.result {
print("Download Starting!")
}
return nil;
}

}

Ref. for upload image

Ref. for download image

Many thanks to jzorz

How to upload to S3 bucket taking a photo with the iOS Camera

Most of the answers are outdated and too complicated. I was struggling with the same problem and finally found a solution.

This works best for me and works on Swift 5.

First of all, let's update the function to upload images to AWS.

func uploadToS3(url: URL) {
let fileArr = url.path.components(separatedBy: "/") // Path will be too long, so you have to separate the elements by / and store in an array
let key = fileArr.last // We get the last element of the array which in our case will be the image (my-image.jpg)
let localImageUrl = url

let request = AWSS3TransferManagerUploadRequest()!
request.bucket = bucketName
request.key = key
request.body = localImageUrl
request.acl = .publicReadWrite

let transferManager = AWSS3TransferManager.default()
transferManager.upload(request).continueWith(executor: AWSExecutor.mainThread()) { (task) -> Any? in
if let error = task.error {
print(error)
}
if task.result != nil {
print("Uploaded \(key)")
let contentUrl = self.s3Url.appendingPathComponent(bucketName).appendingPathComponent(key!)
self.contentUrl = contentUrl
}
return nil
}
}

In this block of code:

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let pickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
takenPhoto.contentMode = .scaleToFill
takenPhoto.image = pickedImage
print(takenPhoto.image = pickedImage)
// Add here:
let url = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
.appendingPathComponent("my-image", isDirectory: false)
.appendingPathExtension("jpg") /* here we are naming the image 'my-image' and it will be 'jpg', if you want you can add a counter to increase the number each time you upload an image, and you make something like this: "my-image-\(counter)"*/

// Then write to disk
if let data = pickedImage.jpegData(compressionQuality: 0.8) {
do {
try data.write(to: url)
uploadToS3(url: url) //Call the updated function to store to AWS bucket
} catch {
print("Handle the error, i.e. disk can be full")
}
}
}
picker.dismiss(animated: true, completion: nil)
}

With this implementation, the image will be uploaded immediately to the server once you select the image from the library.

Show Upload Progress for Image Upload to Amazon S3 using Swift 3 and Amazon SDK

Okay I found the solution to my problem, apparently I just didn't follow through enough.

Here is my new revised uploadImage function

func uploadImage(filename:String){
print("AWS Upload Image Attempt...")
//defining bucket and upload file name
let S3BucketName: String = "distribution-tech-mobile-live"
let filepath = "\(AppDelegate.appDelegate.applicationDocumentsDirectory())/\(filename)"
let imageURL = URL(fileURLWithPath: filepath)
let S3UploadKeyName = filename //TODO: Change this later

let uploadRequest = AWSS3TransferManagerUploadRequest()
uploadRequest?.bucket = S3BucketName
uploadRequest?.key = S3UploadKeyName
uploadRequest?.contentType = "image/jpeg"
uploadRequest?.body = imageURL
uploadRequest?.serverSideEncryption = AWSS3ServerSideEncryption.awsKms
uploadRequest?.uploadProgress = { (bytesSent, totalBytesSent, totalBytesExpectedToSend) -> Void in
DispatchQueue.main.async(execute: {
self.amountUploaded = totalBytesSent // To show the updating data status in label.
self.fileSize = totalBytesExpectedToSend
print("\(totalBytesSent)/\(totalBytesExpectedToSend)")
})
}

let transferManager = AWSS3TransferManager.default()
transferManager?.upload(uploadRequest).continue(with: AWSExecutor.mainThread(), withSuccessBlock: { (taskk: AWSTask) -> Any? in
if taskk.error != nil {
// Error.
print("error")
} else {
// Do something with your result.
print("something with result when its done")
}
return nil
})

}

This has spot for when result is done and during the upload progress and makes a lot more sense.

amazon S3 Swift - simple upload take ages and finally doesnt show up in bucket

ok, was trying to upload NSData file using the uploaddata request, here is the working code with proper URL swift2 conversion :

 func uploadImage(){

let img:UIImage = fullimage!.image!

// create a local image that we can use to upload to s3
let path:NSString = NSTemporaryDirectory().stringByAppendingString("image2.jpg")
let imageD:NSData = UIImageJPEGRepresentation(img, 0.2)!
imageD.writeToFile(path as String, atomically: true)

// once the image is saved we can use the path to create a local fileurl
let url:NSURL = NSURL(fileURLWithPath: path as String)

// next we set up the S3 upload request manager
uploadRequest = AWSS3TransferManagerUploadRequest()
// set the bucket
uploadRequest?.bucket = "witnesstest"
// 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/image2.jpeg"
// set the content type
uploadRequest?.contentType = "image/jpeg"
// 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;
print(self.filesize)
print(self.amountUploaded)
})
}

// now the upload request is set up we can creat the transfermanger, the credentials are already set up in the app delegate
let transferManager:AWSS3TransferManager = AWSS3TransferManager.defaultS3TransferManager()
// start the upload

transferManager.upload(uploadRequest).continueWithBlock { (task) -> AnyObject? in

// once the uploadmanager finishes check if there were any errors
if(task.error != nil){
print("%@", task.error);
}else{ // if there aren't any then the image is uploaded!
// this is the url of the image we just uploaded
print("https://s3.amazonaws.com/witnesstest/foldername/image2.jpeg");
}

return "all done";
}

}

I would like to thank Barrett Breshears for the Help, and his GithubSource is from 2014 but I could manage to convert it easily because of this clear and well commented piece of code



Related Topics



Leave a reply



Submit