使用Alamofire在iOS Swift中发送MultipartFormData的POST参数

73

我第一次使用Alamofire,正在使用最新版本的Alamofire 1.3.1。我想在一个API调用中发送一张图片、一个视频和一些POST参数。我正在使用多部分表单数据。多部分模块正在工作。我遇到了一个问题,无法发送额外的POST参数params。以下是我的代码。"params"是包含额外参数的字典?我该如何将这些POST参数附加到请求中。请帮忙。

        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):

            }
        }
    )

1
可能是重复的问题 https://dev59.com/UV8e5IYBdhLWcg3wEXFi - Victor Sigler
14个回答

85

我终于找到解决方法了 :)。

我们可以将数据作为多部分表单数据附加在请求中。

以下是我的代码。

  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):

            }
        }
    )

编辑 1:对于那些想要发送数组而不是浮点数、整数或字符串的人,他们可以将其数组或任何类型的数据结构转换为 Json 字符串,将此 JSON 字符串作为普通字符串传递。并在后端解析此 Json 字符串以获取原始数组


如果我的参数是一个数组。 - Kamala Dash
你需要将数组转换为字符串。在参数中不能直接发送数组。 - Ankush
3
我可以打印multipartFormData以便了解将要发送到服务器的内容吗? - Nabeel Ahmed
3
有人能否更新一下,以适应Swift 3和Alamofire 4呢?我在将参数传递给append函数时遇到了将参数转换为数据类型的问题。 - Shayan C
@Ankush 嗨兄弟,需要你的帮助。 - Dilip Tiwari
显示剩余4条评论

41

在 Alamofire 4 中,重要的是在添加文件数据之前添加请求体数据!

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,
    [...]
)

音频文件怎么样?我试图以这种形式发送音频文件:multipartFormData.append(audioLocalPath, withName: "file", fileName: "file", mimeType: "application/octet-stream"),但出现了这个错误:multipartEncodingFailed(Alamofire.AFError.MultipartEncodingFailureReason.bodyPartFileNotReachableWithError(file:///var/mobile/Containers/....... /Documents/item.mp3, NSUnderlyingError=0x16049100 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}})) - 问题出在哪里?是坏请求还是坏的音频路径? - Saeid
“没有这样的文件或目录”看起来像是一个错误的文件路径。 - Alexander Scholz
是的!我知道这是一条错误的路径,但我正在尝试用以下方式获取路径:let localPath = audioURL.appendingPathComponent(audioName!),它适用于图像但不适用于音频。 - Saeid
这给了我一个错误:"类型'Any'的值没有成员'data'"。我只是使用了这个替代方案:"multipartFormData.append("\(value)".data(using: .utf8)!, withName: key)"。 - inigo333
7
“在Alamofire 4中,重要的是先添加请求体数据再添加文件数据。”这一行对我意义非凡。由于我先添加了图像再添加请求体,服务器返回400,我被卡了两天。谢谢。但在我早期使用alamofire 4的应用程序中,这种情况并未发生。这是第一次出现,非常奇怪。有人能解释为什么吗?+1 - Rajan Maheshwari
显示剩余3条评论

21

这是我如何解决我的问题

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)
        }


    }

9

Swift 3 / Alamofire 4.0 (附加说明 接受的答案)

在 Swift 3 / Alamofire 4.0 中,要追加数据到 multipartFormData,请使用 MultipartFormData 的以下方法:

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

要将String转换为Data,需要使用Stringdata(using:)方法。例如:

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

1
谢谢。您有带有URLRequestConvertible参数的示例吗? - Dani Pralea
1
我需要追加Int数据,该怎么办?你能帮我吗? - Ekta Padaliya

7

Alamofire 5及以上版本

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)
}

文档链接:分块上传


什么是MultipartResponse? - Ahmadreza
1
这是最新的解决方案,一些答案已经过时了。当前主要的 Alamofire 是 5 版本。应该优先使用它而不是其他解决方案。 - X.Y.

6

对于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)")
    }
}

你也可以看看 CodyFire库,它使用Codable来简化所有API调用。

使用CodyFire进行Multipart调用的示例:

//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{}

您可以查看lib的自述文件中的所有示例,链接地址为https://github.com/MihaelIsaev/CodyFire


5

Swift 5,更新@Ankush的Alamofire代码为:

     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):
                    ()
                }
            }

3

嗯,既然多部分表单数据是用于二进制(而不是文本)数据传输的,我认为在其上以编码形式发送数据是不好的做法。

另一个缺点是无法发送更复杂的参数,比如JSON。

也就是说,更好的选择是以二进制形式发送所有数据,即作为数据(Data)。

比如说我需要发送这些数据:

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

...与用户的照片一起:

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

为此,我会将该文本数据转换为JSON格式,然后与图片一起转换为二进制格式:
//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)

最后,通过多部分表单数据请求发送它:

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

3

如同在 Swift 3.x 中上传带有参数的图像,我们可以使用以下 Alamofire 上传方法 -

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)
            }

        }

    }

注意:如果我们的参数是键值对的数组,则可以使用
 var arrayOfKeyPairs = [[String:Any]]()
 let json = try? JSONSerialization.data(withJSONObject: arrayOfKeyPairs, options: [.prettyPrinted])
 let jsonPresentation = String(data: json!, encoding: .utf8)

1
对于Alamofire 4,请使用以下内容。
        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)

            }
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接