Ankush
Ankush

Reputation: 2555

Send POST parameters with MultipartFormData using Alamofire, in iOS Swift

I am using Alamofire, very first time. I am using the latest version Alamofire 1.3.1. I want to send one image , one video and some POST parameters in one API call. I am using multipart form data. The mutipart module is working. I am facing a problem to send extra POST parametersparams . Below is my code. "params" is the dictionary which contains extra parameters? How can I append these POST parameters in the request. Please help

        var fullUrl :String = Constants.BASE_URL + "/api/CompleteChallenge"
         var params = [
        "authKey": Constants.AuthKey,
        "idUserChallenge": "16",
        "comment": "",
        "photo": imagePath,
        "video": videoPath,
        "latitude": "1",
        "longitude": "1",
        "location": "india"
    ]

    let imagePathUrl = NSURL(fileURLWithPath: imagePath!)
    let videoPathUrl = NSURL(fileURLWithPath: videoPath!)

        Alamofire.upload(
        .POST,
        URLString: fullUrl, // http://httpbin.org/post
        multipartFormData: { multipartFormData in
            multipartFormData.appendBodyPart(fileURL: imagePathUrl!, name: "photo")
            multipartFormData.appendBodyPart(fileURL: videoPathUrl!, name: "video")
        },
        encodingCompletion: { encodingResult in
            switch encodingResult {
            case .Success(let upload, _, _):
                upload.responseJSON { request, response, JSON, error in

                  }
                }
            case .Failure(let encodingError):

            }
        }
    )

Upvotes: 77

Views: 138867

Answers (13)

Raul
Raul

Reputation: 24

 func savePostWithImage(title: String, content: String, image: UIImage){
    let parameters: Parameters = [
        "title":    title,
        "content":  content
    ]
    
    guard let url = URL(string: "http://localhost/proyecto/crud/save.php") else { return }
    
    guard let imgData = image.pngData() else { return }
    
    let imageId = UUID().uuidString
    
    DispatchQueue.main.async {
        AF.upload(multipartFormData: { MultipartFormData in
            MultipartFormData.append(imgData, withName: "Image", fileName: "\(imageId).png", mimeType: "image/png")
            
            for (key, value) in parameters {
                MultipartFormData.append((value as AnyObject).data(using: String.Encoding.utf8.rawValue)!, withName: key)
            }
            
        }, to: url, method: .post).uploadProgress { Progress in
            print(Progress.fractionCompleted * 100)
        }.response { [self] response in
            createdPostAlertMessage = successCreatingPostMessage
            showReponseAlert = true
        }
    }
    
}

Upvotes: -1

Suhit Patil
Suhit Patil

Reputation: 12023

Alamofire 5 and above

AF.upload(multipartFormData: { multipartFormData in
    multipartFormData.append(Data("one".utf8), withName: "one")
    multipartFormData.append(Data("two".utf8), withName: "two")
    multipartFormData.append(imageData, withName: "file", fileName: "photo.jpg", mimeType: "image/jpg")
}, 
to: "https://httpbin.org/post").responseDecodable(of: MultipartResponse.self) { response in
        debugPrint(response)
}

documentation link: multipart upload

Upvotes: 9

kbunarjo
kbunarjo

Reputation: 1375

Alamofire 5 with Array params

The issue for me was that my params could be any type. Converting to them to strings and then getting the data encoding worked for most, but I kept running into issues with arrays. For arrays you have to encode each element with a key denoting its index. This is how I ended up encoding my params so it would be dynamic for any type:

            let image = UIImage(named: "your image") // Change me
            let imageData = image.jpegData(compressionQuality: 1.0)!
            let imageKey = "image_key" // Change me
            let urlString = "https://yourserver.com/yourendpoint/" // Change me
            let params: [String: Any]? = [:] // Change me. Your POST params here
            let headers: HTTPHeaders = [
                // Change me. Your headers here.
            ]
            AF.upload(multipartFormData: { multiPart in
                for (key, value) in (params ?? [:]) {
                    if let arrayObj = value as? [Any] {
                        for index in 0..<arrayObj.count {
                            multiPart.append("\(arrayObj[index])".data(using: .utf8)!, withName: "\(key)[\(index)]")
                        }
                    } else {
                        multiPart.append("\(value)".data(using: .utf8)!, withName: key)
                    }
                }
                multiPart.append(imageData, withName: imageKey, fileName: "file.jpg", mimeType: "image/jpg")
            }, to: urlString, headers: headers).responseJSON { response in
                switch response.result {
                case .success(_):
                    if let dictionary = response.value as? [String:Any] {
                        print("success", dictionary)
                    } else {
                        print("error")
                    }
                case .failure(let error):
                    print("error", error.localizedDescription)
                }
            }

Also note that you have to append the image after appending all your params

Upvotes: 0

Arthur Shkil
Arthur Shkil

Reputation: 362

Well, since Multipart Form Data is intended to be used for binary ( and not for text) data transmission, I believe it's bad practice to send data in encoded to String over it.

Another disadvantage is impossibility to send more complex parameters like JSON.

That said, a better option would be to send all data in binary form, that is as Data.

Say I need to send this data

let name = "Arthur"
let userIDs = [1,2,3]
let usedAge = 20

...alongside with user's picture:

let image = UIImage(named: "img")!

For that I would convert that text data to JSON and then to binary alongside with image:

//Convert image to binary
let data = UIImagePNGRepresentation(image)!

//Convert text data to binary
let dict: Dictionary<String, Any> = ["name": name, "userIDs": userIDs, "usedAge": usedAge]
userData = try? JSONSerialization.data(withJSONObject: dict)

And then, finally send it via Multipart Form Data request:

Alamofire.upload(multipartFormData: { (multiFoormData) in
        multiFoormData.append(userData, withName: "user")
        multiFoormData.append(data, withName: "picture", mimeType: "image/png")
    }, to: url) { (encodingResult) in
        ...
    }

Upvotes: 3

black_pearl
black_pearl

Reputation: 2699

Swift 5, update @Ankush's Alamofire Code to

     var fullUrl = "http://httpbin.org/post" // for example

           Alamofire.upload(multipartFormData: { (multipartFormData) in
                multipartFormData.append( imagePathUrl! , withName: "photo")
                multipartFormData.append( videoPathUrl!,  withName: "video")
                multipartFormData.append(Constants.AuthKey.data(using: .utf8, allowLossyConversion: false)!, withName: "authKey")
                multipartFormData.append("16".data(using: .utf8, allowLossyConversion: false)!, withName: "idUserChallenge")
                multipartFormData.append("111".data(using: .utf8, allowLossyConversion: false)!, withName: "authKey")
                multipartFormData.append("comment".data(using: .utf8, allowLossyConversion: false)!, withName: "comment")
                multipartFormData.append("0.00".data(using: .utf8, allowLossyConversion: false)!, withName: "latitude")
                multipartFormData.append("0.00".data(using: .utf8, allowLossyConversion: false)!, withName: "longitude")
                multipartFormData.append("India".data(using: .utf8, allowLossyConversion: false)!, withName: "location")

            }, to: fullUrl, method: .post) { (encodingResult) in
                switch encodingResult {
                case .success(request: let upload, streamingFromDisk: _, streamFileURL: _):
                    upload.responseJSON { (response) in   // do sth     }
                case .failure(let encodingError):
                    ()
                }
            }

Upvotes: 5

Akshaykumar Maldhure
Akshaykumar Maldhure

Reputation: 1259

Found one more way of doing it

 if let parameters = route.parameters {

                    for (key, value) in parameters {
                         if value is String {
                            if let temp = value as? String {
                                multipartFormData.append(temp.description.data(using: .utf8)!, withName: key)
                            }
                        }
                        else if value is NSArray {
                            if let temp = value as? [Double]{
                                multipartFormData.append(temp.description.data(using: .utf8)!, withName: key)
                            }
                            else if let temp = value as? [Int]{
                                multipartFormData.append(temp.description.data(using: .utf8)!, withName: key)
                            }
                            else if let temp = value as? [String]{
                                multipartFormData.append(temp.description.data(using: .utf8)!, withName: key)
                            }
                        }
                        else if CFGetTypeID(value as CFTypeRef) == CFNumberGetTypeID() {
                            if let temp = value as? Int {
                                multipartFormData.append(temp.description.data(using: .utf8)!, withName: key)
                            }
                        }
                        else if CFGetTypeID(value as CFTypeRef) == CFBooleanGetTypeID(){
                            if let temp = value as? Bool {
                                multipartFormData.append(temp.description.data(using: .utf8)!, withName: key)
                            }
                        }
                    }
                }

                if let items: [MultipartData] = route.multipartData{
                    for item in items {
                        if let value = item.value{
                            multipartFormData.append(value, withName: item.key, fileName: item.fileName, mimeType: item.mimeType)
                        }
                    }
                }

Upvotes: 0

Alexander Scholz
Alexander Scholz

Reputation: 2100

In Alamofire 4 it is important to add the body data before you add the file data!

let parameters = [String: String]()
[...]
self.manager.upload(
    multipartFormData: { multipartFormData in
        for (key, value) in parameters {
            multipartFormData.append(value.data(using: .utf8)!, withName: key)
        }
        multipartFormData.append(imageData, withName: "user", fileName: "user.jpg", mimeType: "image/jpeg")
    },
    to: path,
    [...]
)

Upvotes: 41

imike
imike

Reputation: 5646

For Swift 4.2 / Alamofire 4.7.3

Alamofire.upload(multipartFormData: { multipart in
    multipart.append(fileData, withName: "payload", fileName: "someFile.jpg", mimeType: "image/jpeg")
    multipart.append("comment".data(using: .utf8)!, withName :"comment")
}, to: "endPointURL", method: .post, headers: nil) { encodingResult in
    switch encodingResult {
    case .success(let upload, _, _):
        upload.response { answer in
            print("statusCode: \(answer.response?.statusCode)")
        }
        upload.uploadProgress { progress in
            //call progress callback here if you need it
        }
    case .failure(let encodingError):
        print("multipart upload encodingError: \(encodingError)")
    }
}

Also you could take a look at CodyFire lib it makes API calls easier using Codable for everything. Example for Multipart call using CodyFire

//Declare your multipart payload model
struct MyPayload: MultipartPayload {
    var attachment: Attachment //or you could use just Data instead
    var comment: String
}

// Prepare payload for request
let imageAttachment = Attachment(data: UIImage(named: "cat")!.jpeg(.high)!,
                                 fileName: "cat.jpg",
                                 mimeType: .jpg)
let payload = MyPayload(attachment: imageAttachment, comment: "Some text")

//Send request easily
APIRequest("endpoint", payload: payload)
    .method(.post)
    .desiredStatus(.created) //201 CREATED
    .onError { error in
        switch error.code {
        case .notFound: print("Not found")
        default: print("Another error: " + error.description)
        }
    }.onSuccess { result in
        print("here is your decoded result")
    }
//Btw normally it should be wrapped into an extension
//so it should look even easier API.some.upload(payload).onError{}.onSuccess{}

You could take a look at all the examples in lib's readme

Upvotes: 6

Ankush
Ankush

Reputation: 2555

I found the solution :) finally.

We can append data in the request as multipartformdata.

Below is my code.

  Alamofire.upload(
        .POST,
        URLString: fullUrl, // http://httpbin.org/post
        multipartFormData: { multipartFormData in
            multipartFormData.appendBodyPart(fileURL: imagePathUrl!, name: "photo")
            multipartFormData.appendBodyPart(fileURL: videoPathUrl!, name: "video")
            multipartFormData.appendBodyPart(data: Constants.AuthKey.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!, name :"authKey")
            multipartFormData.appendBodyPart(data: "\(16)".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!, name :"idUserChallenge")
            multipartFormData.appendBodyPart(data: "comment".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!, name :"comment")
            multipartFormData.appendBodyPart(data:"\(0.00)".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!, name :"latitude")
            multipartFormData.appendBodyPart(data:"\(0.00)".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!, name :"longitude")
            multipartFormData.appendBodyPart(data:"India".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!, name :"location")
        },
        encodingCompletion: { encodingResult in
            switch encodingResult {
            case .Success(let upload, _, _):
                upload.responseJSON { request, response, JSON, error in


                }
            case .Failure(let encodingError):

            }
        }
    )

EDIT 1: For those who are trying to send an array instead of float, int or string, They can convert their array or any kind of data-structure in Json String, pass this JSON string as a normal string. And parse this json string at backend to get original array

Upvotes: 85

Jack
Jack

Reputation: 14329

As in Swift 3.x for upload image with parameter we can use below alamofire upload method-

static func uploadImageData(inputUrl:String,parameters:[String:Any],imageName: String,imageFile : UIImage,completion:@escaping(_:Any)->Void) {

        let imageData = UIImageJPEGRepresentation(imageFile , 0.5)

        Alamofire.upload(multipartFormData: { (multipartFormData) in

            multipartFormData.append(imageData!, withName: imageName, fileName: "swift_file\(arc4random_uniform(100)).jpeg", mimeType: "image/jpeg")

            for key in parameters.keys{
                let name = String(key)
                if let val = parameters[name!] as? String{
                    multipartFormData.append(val.data(using: .utf8)!, withName: name!)
                }
            }
        }, to:inputUrl)
        { (result) in
            switch result {
            case .success(let upload, _, _):

                upload.uploadProgress(closure: { (Progress) in
                })

                upload.responseJSON { response in

                    if let JSON = response.result.value {
                        completion(JSON)
                    }else{
                        completion(nilValue)
                    }
                }

            case .failure(let encodingError):
                completion(nilValue)
            }

        }

    }

Note: Additionally if our parameter is array of key-pairs then we can use

 var arrayOfKeyPairs = [[String:Any]]()
 let json = try? JSONSerialization.data(withJSONObject: arrayOfKeyPairs, options: [.prettyPrinted])
 let jsonPresentation = String(data: json!, encoding: .utf8)

Upvotes: 3

Subhojit Mandal
Subhojit Mandal

Reputation: 450

for alamofire 4 use this ..

        Alamofire.upload(multipartFormData: { (multipartFormData) in

            multipartFormData.append(fileUrl, withName: "video")
       //fileUrl is your file path in iOS device and withName is parameter name

        }, to:"http://to_your_url_path")
        { (result) in
            switch result {
            case .success(let upload, _ , _):

                upload.uploadProgress(closure: { (progress) in

                    print("uploding")
                })

                upload.responseJSON { response in

                   print("done")

                }

            case .failure(let encodingError):
                print("failed")
                print(encodingError)

            }
        }

Upvotes: 1

dfrib
dfrib

Reputation: 73176

Swift 3 / Alamofire 4.0 (Addendum to the accepted answer)

To append to multipartFormData in Swift 3 / Alamofire 4.0, use the following method of MultipartFormData:

public func append(_ data: Data, withName name: String) { /* ... */ }

And, to convert String to Data, the data(using:) method of String. E.g.,

multipartFormData.append("comment".data(using: .utf8)!, withName: "comment")

Upvotes: 9

Murat Akdeniz
Murat Akdeniz

Reputation: 434

This is how i solve my problem

let parameters = [
            "station_id" :        "1000",
            "title":      "Murat Akdeniz",
            "body":        "xxxxxx"]

let imgData = UIImageJPEGRepresentation(UIImage(named: "1.png")!,1)



    Alamofire.upload(
        multipartFormData: { MultipartFormData in
        //    multipartFormData.append(imageData, withName: "user", fileName: "user.jpg", mimeType: "image/jpeg")

            for (key, value) in parameters {
                MultipartFormData.append(value.data(using: String.Encoding.utf8)!, withName: key)
            }

        MultipartFormData.append(UIImageJPEGRepresentation(UIImage(named: "1.png")!, 1)!, withName: "photos[1]", fileName: "swift_file.jpeg", mimeType: "image/jpeg")
        MultipartFormData.append(UIImageJPEGRepresentation(UIImage(named: "1.png")!, 1)!, withName: "photos[2]", fileName: "swift_file.jpeg", mimeType: "image/jpeg")


    }, to: "http://platform.twitone.com/station/add-feedback") { (result) in

        switch result {
        case .success(let upload, _, _):

            upload.responseJSON { response in
                print(response.result.value)
            }

        case .failure(let encodingError): break
            print(encodingError)
        }


    }

Upvotes: 21

Related Questions