Alamofire - how to have progress and completion closure with multipart upload - swift2

I managed to upload file with multipart-form-data Alamofire upload:
Alamofire.upload(.POST, "api.myservice.com", headers: myheaders, multipartFormData: { (multipartFormData:MultipartFormData) -> Void in
multipartFormData.appendBodyPart(data: json, name: "metadata", mimeType: "application/json")
multipartFormData.appendBodyPart(data: self.data, name: "document", fileName: "photo.png", mimeType: "image/png")
}, encodingMemoryThreshold: 10 * 1024 * 1024) { (result:Manager.MultipartFormDataEncodingResult) -> Void in
}
but I can't see a way to track upload progress and have completion block called after upload is completed (or failed). Is there a way to do this in Alamofire?
Note: I am aware that uploading with progress is possible, but I'm looking into multipart-form-data specifically.

Here is a way to have completion, failure and progress closures (thanks to my colleague for point me to the solution):
Alamofire.upload(.POST, absPath(), headers: headers(), multipartFormData: { (multipartFormData:MultipartFormData) -> Void in
multipartFormData.appendBodyPart(data: json, name: "metadata", mimeType: "application/json")
multipartFormData.appendBodyPart(data: self.data, name: "document", fileName: "photo.png", mimeType: "image/png")
}, encodingMemoryThreshold: 10 * 1024 * 1024, encodingCompletion: { (encodingResult) -> Void in
switch encodingResult {
case .Success(let upload, _, _):
upload.responseJSON { response in
// success block
}
upload.progress { _, totalBytesRead, totalBytesExpectedToRead in
let progress = Float(totalBytesRead)/Float(totalBytesExpectedToRead)
// progress block
}
case .Failure(_):
// failure block
}
})

The accepted answer is great. This one includes parameters, too:
let image = UIImage(named: "big.jpg")!
let imageData = UIImageJPEGRepresentation(image, 1)
let coupon = textBox?.text ?? "NO coupon"
let uploadUrl = "http://example.com/upload.php"
// define parameters
let parameters : [String: String] = ["one":"some param", "two":"some other param"]
Alamofire.upload(.POST, uploadUrl, headers: nil,
multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(data: imageData!, name: "cannotBeBlank", fileName: "image.zip", mimeType: "image/png")
// import parameters
for (key, value) in parameters {
multipartFormData.appendBodyPart(data: value.data!, name: key)
}
}, encodingCompletion: { encodingResult in
switch encodingResult {
case .Success(let upload, _, _):
upload.progress { bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in
print("1) \(bytesWritten) 2) \(totalBytesWritten) 3) \(totalBytesExpectedToWrite)")
// This closure is NOT called on the main queue for performance
// reasons. To update your ui, dispatch to the main queue.
dispatch_async(dispatch_get_main_queue()) {
print("Total bytes written on main queue: \(totalBytesWritten)")
}
}
upload.responseData(self.handleResponse)
case .Failure:
self.handleError()
}
})

Related

Alamofire 4 multipart

I try to create multipart request using Alamofire 4.
I wont to add [String: AnyObject] as part. But when multipartFormData appending bool or [Int] from my payload dict app crashed! Where is my mistake?
let payload: [String: AnyObject] = [
"key1": "val1" as AnyObject,
"key2": true as AnyObject,
"key3" : [1,2,3,4] as AnyObject
]
let requestString = "http://www.url.com/api/action"
Alamofire.upload(multipartFormData: { (multipartFormData) in
for (key, value) in payload {
multipartFormData.append(value.data(using: String.Encoding.utf8.rawValue, allowLossyConversion: false)!, withName: "request", fileName: key, mimeType: "application/json")
}
multipartFormData.append(imageUrl, withName: "fileData", fileName: "image", mimeType: "image/png")
}, to: requestString, method: .post , headers: ["Content-Type": "multipart/form-data"], encodingCompletion: { (result) in
switch result {
case .success(let upload, _, _):
upload.uploadProgress(closure: { (progress) in
print(progress.fractionCompleted * 100)
})
upload.responseJSON(completionHandler: { (response) in
})
case .failure(let error):
print(error)
}
})
You are sending wrong param. Try below code for Alamofire 4.
let payload: [String: AnyObject] = [
"key1": "val1" as AnyObject,
"key2": true as AnyObject,
"key3" : [1,2,3,4] as AnyObject
]
let requestString = "http://www.url.com/api/action"
Alamofire.upload(multipartFormData: { (multipartFormData) in
multipartFormData.append(UIImageJPEGRepresentation(self.photoImageView.image!, 0.5)!, withName: "photo_path", fileName: "swift_file.jpeg", mimeType: "image/jpeg")
for (key, value) in payload {
multipartFormData.append(value.data(using: String.Encoding.utf8)!, withName: key)
}
}, to: requestString, method: .post , headers:nil, encodingCompletion: { (result) in
switch result {
case .success(let upload, _, _):
upload.uploadProgress(closure: { (progress) in
print(progress.fractionCompleted * 100)
})
upload.responseJSON(completionHandler: { (response) in
})
case .failure(let error):
print(error)
}
})

SWIFT2 + Alamofire3 Upload photo, simple method

I am trying to upload photo to server with this method from alamo doc
let fileURL = NSBundle.mainBundle().URLForResource("Default", withExtension: "png")
Alamofire.upload(.POST, "https://httpbin.org/post", file: fileURL)
I edit method to my needs like this
let fileURL = NSBundle.mainBundle().URLForResource(filePath, withExtension: "png")
Alamofire.upload(.POST, urlDomain + "api/user/", parameters: parameters, headers: headers, file: fileURL)
What am I missing? I just wanna to sent 1 photo to server. Nothin more.
I found an answer on SO that I've update to Alamofire 3:
//
// FileUploader.swift
//
// Copyright (c) 2015 Narciso Cerezo Jiménez. All rights reserved.
// Largely based on this stackoverflow question: http://stackoverflow.com/questions/26121827/uploading-file-with-parameters-using-alamofire/28467829//
import Foundation
import Alamofire
private struct FileUploadInfo {
var name:String
var mimeType:String
var fileName:String
var url:NSURL?
var data:NSData?
init( name: String, withFileURL url: NSURL, withMimeType mimeType: String? = nil ) {
self.name = name
self.url = url
self.fileName = name
self.mimeType = "application/octet-stream"
if mimeType != nil {
self.mimeType = mimeType!
}
if let _name = url.lastPathComponent {
fileName = _name
}
if mimeType == nil, let _extension = url.pathExtension {
switch _extension.lowercaseString {
case "jpeg", "jpg":
self.mimeType = "image/jpeg"
case "png":
self.mimeType = "image/png"
default:
self.mimeType = "application/octet-stream"
}
}
}
init( name: String, withData data: NSData, withMimeType mimeType: String ) {
self.name = name
self.data = data
self.fileName = name
self.mimeType = mimeType
}
}
class FileUploader {
private var parameters = [String:String]()
private var files = [FileUploadInfo]()
private var headers = [String:String]()
func setValue( value: String, forParameter parameter: String ) {
parameters[parameter] = value
}
func setValue( value: String, forHeader header: String ) {
headers[header] = value
}
func addParametersFrom( map map: [String:String] ) {
for (key,value) in map {
parameters[key] = value
}
}
func addHeadersFrom( map map: [String:String] ) {
for (key,value) in map {
headers[key] = value
}
}
func addFileURL( url: NSURL, withName name: String, withMimeType mimeType:String? = nil ) {
files.append( FileUploadInfo( name: name, withFileURL: url, withMimeType: mimeType ) )
}
func addFileData( data: NSData, withName name: String, withMimeType mimeType:String = "application/octet-stream" ) {
files.append( FileUploadInfo( name: name, withData: data, withMimeType: mimeType ) )
}
func uploadFile( request sourceRequest: NSURLRequest ) -> Request? {
var request = sourceRequest.mutableCopy() as! NSMutableURLRequest
let boundary = "FileUploader-boundary-\(arc4random())-\(arc4random())"
request.setValue( "multipart/form-data;boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
let data = NSMutableData()
for (name, value) in headers {
request.setValue(value, forHTTPHeaderField: name)
}
// Amazon S3 (probably others) wont take parameters after files, so we put them first
for (key, value) in parameters {
data.appendData("\r\n--\(boundary)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
data.appendData("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n\(value)".dataUsingEncoding(NSUTF8StringEncoding)!)
}
for fileUploadInfo in files {
data.appendData( "\r\n--\(boundary)\r\n".dataUsingEncoding(NSUTF8StringEncoding)! )
data.appendData( "Content-Disposition: form-data; name=\"\(fileUploadInfo.name)\"; filename=\"\(fileUploadInfo.fileName)\"\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
data.appendData( "Content-Type: \(fileUploadInfo.mimeType)\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
if fileUploadInfo.data != nil {
data.appendData( fileUploadInfo.data! )
}
else if fileUploadInfo.url != nil, let fileData = NSData(contentsOfURL: fileUploadInfo.url!) {
data.appendData( fileData )
}
else { // ToDo: report error
return nil
}
}
data.appendData("\r\n--\(boundary)--\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
return Alamofire.upload( request, data: data )
}
}
This should work for you too!

Alamofire 2.0 Multipart Form Data Error

This code work before migrate to Swift 2.0 and Alamofire 2.0
manager.upload(requestMethod, NSURL(string: url)!, multipartFormData: { multipartFormData in
for param in params {
multipartFormData.appendBodyPart(data: param.1.dataUsingEncoding(NSUTF8StringEncoding)!, name: param.0)
}
multipartFormData.appendBodyPart(data: imageData!, name: "file", fileName: "tempImage", mimeType: "image/*") },
encodingCompletion: { encodingResult in
switch encodingResult {
case .Success(let upload, _, _):
upload.responseJSON { _, response, result in
switch result {
case .Success(let data):
...
case .Failure(let encodingError):
...
}
}
case .Failure(let encodingError):
...
}
} )
Now the line:
upload.responseJson...
always return fail "FAILURE: Error Domain=NSURLErrorDomain Code=-999 "cancelled" "
Someone has managed to use the multipart in Alamofire 2.0 successfully and know what am I doing wrong?
In my case it had to do with additional headers. I put general headers like this:
var defaultHeaders = Alamofire.Manager.sharedInstance.session.configuration.HTTPAdditionalHeaders ?? [:]
defaultHeaders["User-Agent"] = userAgent
if let ip = ifAddress {
defaultHeaders["X-Forwarded-For"] = ip
}
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = defaultHeaders
manager = Alamofire.Manager(configuration: configuration)
And for additional headers that depend on a specific condition I put in the request itself:
if condition {
headers = ["Accept": contentType]
}
manager!.request(requestMethod, url, parameters: params, headers: headers).responseJSON { response in
…

How to upload image with parameters using Alamofire in Swift

I am developing an iPhone application with swift. and I'am using Alamofire framework for handling http requests. I use Alamofire.request for POST , GET and etc like this:
Alamofire.request(.POST, myURL , parameters: ["a": "1", "b" : "2" ])
.response { (request, response, data, error) in
}
And I use Alamofire.upload to upload image to server this :
Alamofire.upload(.POST, uploadURL , fileURL)
And both works perfectly, but now I want to upload an image and also send some parameters with, and my content type should be multipart/form-data and Alamofire.upload does not accept parameters.
There are two more question on SO about this issue with swift, which first one is not using Alamofire (and really, why not?) and in second one, mattt (Alamofire Developer) cited to use encoding parameters.
I checked his example, but still couldn't figure out how to do that.
Can any one please help me solve this problem?
Thank you! :)
you can use Alamofire 3.0+ below code
func uploadImageAndData(){
//parameters
let gender = "M"
let firstName = "firstName"
let lastName = "lastName"
let dob = "11-Jan-2000"
let aboutme = "aboutme"
let token = "token"
var parameters = [String:AnyObject]()
parameters = ["gender":gender,
"firstName":firstName,
"dob":dob,
"aboutme":about,
"token":token,
"lastName":lastName]
let URL = "http://yourserviceurl/"
let image = UIImage(named: "image.png")
Alamofire.upload(.POST, URL, multipartFormData: {
multipartFormData in
if let imageData = UIImageJPEGRepresentation(image, 0.6) {
multipartFormData.appendBodyPart(data: imageData, name: "image", fileName: "file.png", mimeType: "image/png")
}
for (key, value) in parameters {
multipartFormData.appendBodyPart(data: value.dataUsingEncoding(NSUTF8StringEncoding)!, name: key)
}
}, encodingCompletion: {
encodingResult in
switch encodingResult {
case .Success(let upload, _, _):
print("s")
upload.responseJSON {
response in
print(response.request) // original URL request
print(response.response) // URL response
print(response.data) // server data
print(response.result) // result of response serialization
if let JSON = response.result.value {
print("JSON: \(JSON)")
}
}
case .Failure(let encodingError):
print(encodingError)
}
})
}
SWIFT 2 AlamoFire Simple Image Upload (REST API)
#amit gupta It seems answer contains big overhead. AlamoFire contain load of simplified solution. Alamofire.request method contains several simplified overload which can use to upload in simple manner. By using Alamofire.request( method developer can get rid of encoding overhead.
HTTP Status 415 gives because of not specifying the correct media type.
Please check my solution below.
import UIKit
import Alamofire
class ViewController: UIViewController {
#IBOutlet var imageView: UIImageView!
#IBOutlet var btnUpload: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
func successDataHandler(responseData:String){
print ("IMAGE UPLOAD SUCCESSFUL !!!")
}
func failureDataHandler(errorData:String){
print (" !!! IMAGE UPLOAD FAILURE !!! ")
}
#IBAction func actionUpload(sender: AnyObject) {
let URL = "http://m8coreapibeta.azurewebsites.net/api/cards/SaveImages"
let postDataProlife:[String:AnyObject] = ["CardId":(dataCardDetail?.userId)!,"ImageType":1,"ImageData":imageView.image!]
uplaodImageData(URL, postData: postDataProlife, successHandler: successDataHandler, failureHandler: failureDataHandler)
}
func uplaodImageData(RequestURL: String,postData:[String:AnyObject]?,successHandler: (String) -> (),failureHandler: (String) -> ()) -> () {
let headerData:[String : String] = ["Content-Type":"application/json"]
Alamofire.request(.POST,RequestURL, parameters: postData, encoding: .URLEncodedInURL, headers: headerData).responseString{ response in
switch response.result {
case .Success:
print(response.response?.statusCode)
successHandler(response.result.value!)
case .Failure(let error):
failureHandler("\(error)")
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Almaofire with swift 3.0
Alamofire.upload(multipartFormData: { multipartFormData in
var index = 1
for image in imageArray {
let imageData: Data = (UIImageJPEGRepresentation(image, 1.0) as Data?)!
multipartFormData.append(imageData, withName: "home-\(index)", fileName: "home-\(index)", mimeType: "image/jpeg")
index += 1
}
}, with: requestName, encodingCompletion: { result in
switch result {
case .success(let upload, _, _):
upload.responseJSON { response in
print("Image(s) Uploaded successfully:\(response)")
}
case .failure(let encodingError):
print("encodingError:\(encodingError)")
}
})
Swift 4 with Alamofire 4
let isConnected = connectivity.isConnectedToInternet()
func updateProfile(firstName:String,lastName:String ,imageData:Data?,completion: #escaping (isValidUser)->()) {
if self.isConnected {
var parameters : [String:String] = [:]
parameters["auth_key"] = loginUser?.authKey!
parameters["first_name"] = firstName
parameters["last_name"] = lastName
let url = "\(baseUrl)\(basicAuthenticationUrl.updateProfile)"
print(url)
Alamofire.upload(multipartFormData: { (multipartFormData) in
for (key, value) in parameters {
multipartFormData.append("\(value)".data(using: String.Encoding.utf8)!, withName: key as String)
}
if let data = imageData {
multipartFormData.append(data, withName: "image_url", fileName: "image.png", mimeType: "image/png")
}
}, usingThreshold: UInt64.init(), to: url, method: .post) { (result) in
switch result{
case .success(let upload, _, _):
upload.responseJSON { response in
print("Succesfully uploaded = \(response)")
if let err = response.error{
print(err)
return
}
}
case .failure(let error):
print("Error in upload: \(error.localizedDescription)")
}
}
}
}
Almaofire with swift 2.0 just copy and paste below code.here m asumming JSON response from server
func uploadImageRemote (imageData : NSData?) -> Dictionary <String,AnyObject>{
var imageDictionary = Dictionary<String,AnyObject>()
var tokenHeaders:[String:String]! = ["x-access-token":Constants.kUserDefaults.stringForKey("userToken")!]
Alamofire.upload(
.POST,
"http://52.26.230.146:3300/api/profiles/imageUpload",headers:tokenHeaders,
multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(data: imageData!, name: "upload", fileName: "imageFileName.jpg", mimeType: "image/jpeg")
},
encodingCompletion: { encodingResult in
switch encodingResult {
case .Success(let upload, _, _):
upload.progress { (bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) in
print("Uploading Avatar \(totalBytesWritten) / \(totalBytesExpectedToWrite)")
dispatch_async(dispatch_get_main_queue(),{
})
}
upload.responseJSON { response in
guard response.result.error == nil else {
print("error calling GET \(response.result.error!)")
return
}
if let value = response.result.value {
print("Success JSON is:\(value)")
if let result = value as? Dictionary<String, AnyObject> {
imageDictionary["imageUrl"] = result["url"]
}
}
dispatch_async(dispatch_get_main_queue(),{
//Show Alert in UI
print("Avatar uploaded");
})
}
case .Failure(let encodingError):
//Show Alert in UI
print("Avatar not uploaded \(encodingError)");
}
}
);
return imageDictionary
}
To use encoding Parameters, make a ParameterEncoding variable, assign it a encoding type (case of the enum which include .JSON, .URL) and then use the encode function with your NSURLRequest and the parameters. This function returns a tuple of two element, the first being the resulting NSURLRequest and the second being the resulting possible NSError.
Here's how I used it for a custom header I needed in a project
var parameterEncoding:ParameterEncoding!
switch method {
case .POST, .PUT :
parameterEncoding = ParameterEncoding.JSON
default :
parameterEncoding = ParameterEncoding.URL
}
var alamoRequest = Alamofire.Manager.sharedInstance.request(parameterEncoding.encode(mutableURLRequest, parameters: parameters).0)

Fine Uploader - This is an unrecoverable error, we must restart the upload entirely on the next retry attempt

Using Fine Uploader 5.0.6 i am trying to send both the original and scaled image onto my s3 bucket. it works until i set a minimum size limit with minSizeLimit in the validation options. When i leave this out or uncomment it it works fine but when it's in i get this output in my console:
POST http://zippi.bucket.artist.s3-eu-west-1.amazonaws.com/ 400 (Bad Request)
[Fine Uploader 5.0.6] This is an unrecoverable error, we must restart the upload entirely on the next retry attempt.
I have retry:EnableAuto:true so this runs through 3 times with the same result until stopping. This only applies to the scaled image, the main image uploads fine without issue
My signature.php page does not include any references to a minimum file size, just maximum in
$expectedMaxSize = 10000000;
In this function below. Could this be causing it?:
function isPolicyValid($policy) {
global $expectedMaxSize, $expectedBucketName;
$conditions = $policy["conditions"];
$bucket = null;
$parsedMaxSize = null;
for ($i = 0; $i < count($conditions); ++$i) {
$condition = $conditions[$i];
if (isset($condition["bucket"])) {
$bucket = $condition["bucket"];
}
else if (isset($condition[0]) && $condition[0] == "content-length-range") {
$parsedMaxSize = $condition[2];
}
}
return $bucket == $expectedBucketName && $parsedMaxSize == (string)$expectedMaxSize;
}
Code on my actual page:
var manualuploader = jQuery("#fine-uploader").fineUploaderS3({
request: {
endpoint: 's3.bucket.files.s3-eu-west-1.amazonaws.com',
accessKey: 'XXXXXXXXXXXX'
},
signature: {
endpoint: 'signature.php'
},
uploadSuccess: {
endpoint: 'success.php'
},
iframeSupport: {
localBlankPagePath: 'success.html'
},
multiple: false,
retry: {
enableAuto: true
},
autoUpload: false,
scaling: {
sizes: [
{name: "web", maxSize: 500}
]
},
validation: {
allowedExtensions: ['jpeg', 'jpg', 'png'],
sizeLimit: 10000000,
minSizeLimit: 400000 // THIS IS THE LINE
},
objectProperties: {
key: function (fileId) {
var filename = jQuery('#fine-uploader').fineUploader('getName', fileId);
var uuid = jQuery('#fine-uploader').fineUploader('getUuid', fileId);
var ext = filename.substr(filename.lastIndexOf('.') + 1);
folder_name = folder_name.replace(/\s/g, '-');
var new_filename;
if (filename.indexOf('(web)') >= 0){
new_filename = '/the_web_version.';
}
else {
new_filename = '/the_original_version.';
}
return artist + '/' + folder_name + new_filename + ext;
}
}
}).on('submitted', function(event, id, name) {
//....
}).on('cancel', function(event, id, name) {
//....
}).on('progress', function(event, id, fileName, loaded, total) {
//....
}).on('complete', function(event, id, name, response) {
if (response.success) {
//...
}
});
jQuery('#triggerUpload').click(function() {
manualuploader.fineUploaderS3('uploadStoredFiles');
});
If you set a minimum file size validation option, Fine Uploader will pass that value on to S3 to ensure it is respected. Most likely, a scaled image is smaller than that minimum size. I suggest simply removing this restriction if you are dealing with dynamically scaled images.

Resources