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
Saving Audio After Effect in iOS
Sectioning Tableview and Rows with Core Data Swift
iOS with Parse. Pfuser.Currentuser() Not Getting Cached. Returns Nil After App Restart
How to Pass in a Void Block to Objc_Setassociatedobject in Swift
Wake Up Application in Background Using Audiosession Like Alarmy iOS App
How to Download a PDF File in Swift and Find in File Manager
How to Implement a Payment App with Braintree in iOS
Swiftui Multiple Navigationlinks in Form/Sheet - Entry Stays Highlighted
Nsubiquityidentitydidchangenotification and Sigkill
Change Title of a Navigation Bar Button Item
Automatically Change Cell Height Based on Content - Swift
Get All List of Uiviewcontrollers in iOS Swift
How to Add Images as Text Attachment in Swift Using Nsattributedstring
Shift Avplayer Captions When Subview Overlaps Them
Double Tap Necessary to Select Tableview Item with Search Bar