You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Alamofire 5.2多图+参数Multipart表单上传问题求助

Alamofire 5.2 多图上传及进度监听问题解答

问题背景

我正尝试使用Alamofire 5.2向服务器上传多张图片及参数,服务器期望的格式为:

[{"key":"media1","description":"","type":"file","value":["/C:/Users/x/x/x/x.jpg"]}, {"key":"media2","description":"","type":"file","value":["/C:/Users/x/x/x/x.jpg"]}, {"key":"media3","description":"","type":"file","value":["/C:/Users/x/x/x/x.jpg"]}, {"key":"model","value":"{\"emp_Id\": \"6\",\"device_Identifier\":\"Device 01\",\"timestamp\":\"123\",\"description\":\"description\",\"handoverStatus\":\"false\"}","type":"text"}]

我将选中的图片添加到数组的代码如下:

var dictImage : [UIImage]?
if pickedImage.pngData() != nil {
    dictImage!.append(pickedImage)
}

我当前调用Alamofire上传的函数如下:

func xyz() {
    let param : [String: Any] = [
        "emp_Id" : "",
        "device_Identifier" : "",
        "timestamp" : "",
        "description" : "",
        "handoverStatus" : ""
    ]
    let urlString = URL(string: APIDetails.BaseUrl + "api/123")!
    let url = (try? URLRequest(url: urlString, method: .post, headers: APIDetails.header))!
    AF.upload(multipartFormData: { (multipartData) in
        let img1 = arrImage![0]
        let img2 = arrImage![1]
        let img3 = arrImage![2]
        multipartData.append(img1.pngData(), withName:"media1", fileName: "\(Date().timeIntervalSince1970).jpg", mimeType: "file")
        multipartData.append(img2.pngData(), withName:"media2", fileName: "\(Date().timeIntervalSince1970).jpg", mimeType: "file")
        multipartData.append(img3.pngData(), withName:"media3", fileName: "\(Date().timeIntervalSince1970).jpg", mimeType: "file")
        for (key, value) in param {
            multipartData.append((value as AnyObject).data(using: String.Encoding.utf8.rawValue)!, withName: key)
        }
    }, with: url)
}

我有以下问题:

  1. 如何以字典形式上传每张图片及其详情?
  2. 如何添加上传进度监听及结果反馈(判断上传成功或失败)?
    我尝试添加encodingCompletion回调:
}, with: url,encodingCompletion:{ EncodingResult in
    switch EncodingResult{
    case .success(let upload, _, _):
        upload.responseJSON { response in
            debugPrint("SUCCESS RESPONSE: \(response)")
        }
    case .failure(let encodingError):
        print("ERROR RESPONSE: \(encodingError)")
}
})

但出现报错:Extra argument 'encodingCompletion' in call。这是我第一次尝试上传Multipart表单数据,部分代码参考自其他回答。


问题1:按服务器期望的字典格式上传图片及参数

首先得明确服务器的要求:所有数据(图片+文本参数)都要以数组字典的形式提交,其中图片对应mediaN的字典项,文本参数打包成model的JSON字符串。我们需要调整MultipartFormData的构造逻辑:

步骤1:把文本参数转为JSON字符串

服务器的model字段value是一个JSON字符串,所以先把你的参数字典转成符合要求的字符串:

let paramDict: [String: Any] = [
    "emp_Id": "6",
    "device_Identifier": "Device 01",
    "timestamp": "123",
    "description": "description",
    "handoverStatus": "false"
]

// 转成JSON字符串,失败则直接返回
guard let modelData = try? JSONSerialization.data(withJSONObject: paramDict),
      let modelString = String(data: modelData, encoding: .utf8) else {
    print("参数转JSON失败")
    return
}

步骤2:按服务器格式构造图片字段

服务器要求每个图片对应description、type、value(数组)三个字段,所以在Multipart表单里要按mediaN[字段名]的格式append数据:

// 在multipartFormData闭包里处理图片
for (index, image) in arrImage.enumerated() {
    guard let imageData = image.pngData() else { continue }
    let mediaKey = "media\(index + 1)"
    
    // 添加上description(空字符串)
    multipartData.append("".data(using: .utf8)!, withName: "\(mediaKey)[description]")
    // 添加上type(固定为file)
    multipartData.append("file".data(using: .utf8)!, withName: "\(mediaKey)[type]")
    // 添加上图片文件,注意value是数组,所以字段名要加[]
    multipartData.append(imageData, 
                        withName: "\(mediaKey)[value][]", 
                        fileName: "\(Date().timeIntervalSince1970)_media\(index+1).jpg", 
                        mimeType: "image/jpeg") // 这里用image/jpeg更规范,不要用file
}

步骤3:添加上model参数

同样按照服务器的格式,给model添加value和type字段:

multipartData.append(modelString.data(using: .utf8)!, withName: "model[value]")
multipartData.append("text".data(using: .utf8)!, withName: "model[type]")

问题2:添加上传进度监听和结果反馈

你之前用encodingCompletion报错是因为Alamofire 5.x已经废弃了这个回调,现在改用链式调用uploadProgress和response系列方法即可。

完整的上传函数示例

func uploadMedia() {
    // 先检查图片数组是否为空,避免崩溃
    guard let selectedImages = arrImage, !selectedImages.isEmpty else {
        print("请先选择图片")
        return
    }
    
    // 1. 构造并转换文本参数
    let paramDict: [String: Any] = [
        "emp_Id": "6",
        "device_Identifier": "Device 01",
        "timestamp": "123",
        "description": "description",
        "handoverStatus": "false"
    ]
    
    guard let modelData = try? JSONSerialization.data(withJSONObject: paramDict),
          let modelString = String(data: modelData, encoding: .utf8) else {
        print("参数转JSON失败")
        return
    }
    
    // 2. 构造请求URL和Request
    guard let url = URL(string: APIDetails.BaseUrl + "api/123"),
          var urlRequest = try? URLRequest(url: url, method: .post, headers: APIDetails.header) else {
        print("URL或请求构造失败")
        return
    }
    
    // 3. 发起上传请求,链式处理进度和结果
    AF.upload(multipartFormData: { multipartData in
        // 处理图片
        for (index, image) in selectedImages.enumerated() {
            guard let imageData = image.pngData() else { continue }
            let mediaKey = "media\(index + 1)"
            
            multipartData.append("".data(using: .utf8)!, withName: "\(mediaKey)[description]")
            multipartData.append("file".data(using: .utf8)!, withName: "\(mediaKey)[type]")
            multipartData.append(imageData, 
                                withName: "\(mediaKey)[value][]", 
                                fileName: "\(Date().timeIntervalSince1970)_media\(index+1).jpg", 
                                mimeType: "image/jpeg")
        }
        
        // 处理model参数
        multipartData.append(modelString.data(using: .utf8)!, withName: "model[value]")
        multipartData.append("text".data(using: .utf8)!, withName: "model[type]")
    }, with: urlRequest)
    // 监听上传进度
    .uploadProgress { progress in
        let progressPercent = progress.fractionCompleted * 100
        print("当前上传进度:\(String(format: "%.2f", progressPercent))%")
        
        // 更新UI要回到主线程
        DispatchQueue.main.async {
            // 比如更新进度条:self.progressView.progress = progress.fractionCompleted
        }
    }
    // 处理上传结果
    .responseJSON { response in
        switch response.result {
        case .success(let responseData):
            debugPrint("上传成功!返回数据:\(responseData)")
            // 这里写上传成功后的业务逻辑,比如弹窗提示、跳转页面等
        case .failure(let error):
            print("上传失败!错误信息:\(error.localizedDescription)")
            // 处理失败逻辑,比如提示用户重新上传
        }
    }
}

额外的小优化:修复图片数组初始化问题

你之前的dictImage是可选类型,直接强制解包容易崩溃,建议改成非可选的空数组:

// 原来的代码
// var dictImage : [UIImage]?
// if pickedImage.pngData() != nil {
//     dictImage!.append(pickedImage)
// }

// 修改后的代码
var dictImage: [UIImage] = [] // 初始化空数组
if pickedImage.pngData() != nil {
    dictImage.append(pickedImage)
}

内容的提问来源于stack exchange,提问作者Zyfe3r

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 12:27:50