使用Swift在iOS中使用多部分表单数据上传图像

59

我在使用multipart-form上传图像时遇到了问题。

这是我从这个答案中使用的代码:

    var request = NSMutableURLRequest(URL: url!)
    request.HTTPMethod = "POST"

    var boundary = generateBoundaryString()
    request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

    var body = NSMutableData()

    if self.img.image != nil {
        var imageData = UIImagePNGRepresentation(self.img.image)

        if imageData != nil {
            body.appendString("--\(boundary)\r\n")
            body.appendString("Content-Disposition: form-data; name=\"image\"; filename=\"image.png\"\r\n")
            body.appendString("Content-Type: image/png\r\n\r\n")
            body.appendData(imageData!)
            body.appendString("\r\n")
        }

    }

    body.appendString("--\(boundary)--\r\n")
    request.setValue("\(body.length)", forHTTPHeaderField:"Content-Length")
    request.HTTPBody = body

然后我使用NSURLSession来发出请求。

服务器说我没有选择要上传的图像,我现在只想上传这张图片。

我是否需要使用图像的路径来上传任何图像,或者仅使用数据就足够了?

我是否遗漏了什么,需要帮助理解吗?

13个回答

0

非常好的视频和代码:

https://www.youtube.com/watch?v=8GH0yMPvQFU

https://github.com/Kilo-Loco/URLSessionMPFD/blob/master/URLSessionMPFD/ViewController.swift

import UIKit

typealias Parameters = [String: String]

class ViewController: UIViewController {
    

    @IBAction func getRequest(_ sender: Any) {
        guard let url = URL(string: "https://jsonplaceholder.typicode.com/users") else { return }
        var request = URLRequest(url: url)
        
        let boundary = generateBoundary()
        
        request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
        
        let dataBody = createDataBody(withParameters: nil, media: nil, boundary: boundary)
        request.httpBody = dataBody
        
        let session = URLSession.shared
        session.dataTask(with: request) { (data, response, error) in
            if let response = response {
                print(response)
            }
            
            if let data = data {
                do {
                    let json = try JSONSerialization.jsonObject(with: data, options: [])
                    print(json)
                } catch {
                    print(error)
                }
            }
        }.resume()
        
    }
    
    
    @IBAction func postRequest(_ sender: Any) {
        
        let parameters = ["name": "MyTestFile123321",
                          "description": "My tutorial test file for MPFD uploads"]
        
        guard let mediaImage = Media(withImage: #imageLiteral(resourceName: "testImage"), forKey: "image") else { return }
        
        guard let url = URL(string: "https://api.imgur.com/3/image") else { return }
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        
        let boundary = generateBoundary()
        
        request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
        request.addValue("Client-ID f65203f7020dddc", forHTTPHeaderField: "Authorization")
        
        let dataBody = createDataBody(withParameters: parameters, media: [mediaImage], boundary: boundary)
        request.httpBody = dataBody
        
        let session = URLSession.shared
        session.dataTask(with: request) { (data, response, error) in
            if let response = response {
                print(response)
            }
            
            if let data = data {
                do {
                    let json = try JSONSerialization.jsonObject(with: data, options: [])
                    print(json)
                } catch {
                    print(error)
                }
            }
            }.resume()
    }
    
    func generateBoundary() -> String {
        return "Boundary-\(NSUUID().uuidString)"
    }
    
    func createDataBody(withParameters params: Parameters?, media: [Media]?, boundary: String) -> Data {
        
        let lineBreak = "\r\n"
        var body = Data()
        
        if let parameters = params {
            for (key, value) in parameters {
                body.append("--\(boundary + lineBreak)")
                body.append("Content-Disposition: form-data; name=\"\(key)\"\(lineBreak + lineBreak)")
                body.append("\(value + lineBreak)")
            }
        }
        
        if let media = media {
            for photo in media {
                body.append("--\(boundary + lineBreak)")
                body.append("Content-Disposition: form-data; name=\"\(photo.key)\"; filename=\"\(photo.filename)\"\(lineBreak)")
                body.append("Content-Type: \(photo.mimeType + lineBreak + lineBreak)")
                body.append(photo.data)
                body.append(lineBreak)
            }
        }
        
        body.append("--\(boundary)--\(lineBreak)")
        
        return body
    }
    
}


extension Data {
    mutating func append(_ string: String) {
        if let data = string.data(using: .utf8) {
            append(data)
        }
    }
}


struct Media {
    let key: String
    let filename: String
    let data: Data
    let mimeType: String
    
    init?(withImage image: UIImage, forKey key: String) {
        self.key = key
        self.mimeType = "image/jpeg"
        self.filename = "kyleleeheadiconimage234567.jpg"
        
        guard let data = UIImageJPEGRepresentation(image, 0.7) else { return nil }
        self.data = data
    }
    
}

struct Media {
    let key: String
    let filename: String
    let data: Data
    let mimeType: String
    
    init?(withImage image: UIImage, forKey key: String) {
        self.key = key
        self.mimeType = "image/jpeg"
        self.filename = "kyleleeheadiconimage234567.jpg"
        
        guard let data = UIImageJPEGRepresentation(image, 0.7) else { return nil }
        self.data = data
    }
    
}


0
func getHeaders(inAuthToken: String = "") -> HTTPHeaders {
    var header : HTTPHeaders = [:]
    header["Authorization"] = "Bearer \(self.token)"
    print(header)
    return header
}
func putRequestWithMultipart(url: String, parameters:[String: Any], completion: @escaping(Bool) -> Void) {        
    let url = "http://3.6.147.149:3000/api/v1/\(url)"

//

        for (key, value) in parameters {
            if let temp = value as? String {
                multipartFormData.append(temp.data(using: .utf8)!, withName: key )
            }
            if let temp = value as? Int {
                multipartFormData.append("\(temp)".data(using: .utf8)!, withName: key )
            }
            if let temp = value as? NSArray {
                temp.forEach({ element in
                    let keyObj = key + "[]"
                    if let string = element as? String {
                        multipartFormData.append(string.data(using: .utf8)!, withName: keyObj)
                    } else
                    if let num = element as? Int {
                        let value = "\(num)"
                        multipartFormData.append(value.data(using: .utf8)!, withName: keyObj)
                    }
                })
            }
            if let data = value as? Data {
                 if key == "image" {
                    multipartFormData.append(data, withName: "image", fileName: "\(Date.init().timeIntervalSince1970).png", mimeType: "image/png")
                }
            }
        }
    },
           usingThreshold: UInt64.init(),
           to: url,
           method: .put,
           headers: headers
           
    ) { (result) in
        switch result {
        case .success(let upload, _, _):
            upload.uploadProgress(closure: { (progress) in
                print("Upload Progress: \(progress.fractionCompleted)")
            })
            
            upload.responseJSON { response in
                print(response)
                if let data = response.data {
                    do {
                        let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:Any] ?? [:]
                        print(json)
                    } catch {
                        print("Something went wrong")
                    }
                    completion(true)
                }
            }
        case .failure(let encodingError):
            print(encodingError)
            completion(false)
        }
    }
}

}


你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心找到有关如何编写良好答案的更多信息。 - Community

0
经过查看以上所有的示例,提出了一个解决方案。以下是gist
Swift 5.7
调用示例
func upload(imageData: Data) async throws {
    guard let url = URL(string: "https://example.net/upload") else { return }
    var multipartForm = MultipartFormData()
    multipartForm.addField(named: "Content-Type", value: "image/png")
    multipartForm.addField(named: "file", filename: "profile.png", data: imageData)
    let request = URLRequest(url: url, multipartFormData: multipartForm)
    _ = try await URLSession.shared.data(for: request)
}

代码片段:
struct MultipartFormData {
    let boundary: String = UUID().uuidString
    private(set) var httpBody = Data()

    mutating func addField(named name: String, value: String) {
        httpBody.addField("--\(boundary)")
        httpBody.addField("Content-Disposition: form-data; name=\"\(name)\"")
        httpBody.addField("Content-Type: text/plain; charset=ISO-8859-1")
        httpBody.addField("Content-Transfer-Encoding: 8bit")
        httpBody.addField(value)
    }

    mutating func addField(named name: String, filename: String, data: Data) {
        httpBody.addField("--\(boundary)")
        httpBody.addField("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(filename)\"")
        httpBody.addField(data)
    }
}

extension URLRequest {
    init(url: URL, timeoutInterval: TimeInterval = 60, multipartFormData: MultipartFormData) {
        self.init(url: url, timeoutInterval: timeoutInterval)
        let boundary = multipartFormData.boundary
        httpMethod = "POST"
        httpShouldHandleCookies = false
        setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
        var body = multipartFormData.httpBody
        body.append("--\(boundary)--")
        httpBody = body
    }
}

fileprivate extension Data {
    mutating func append(_ string: String) {
        guard let data = string.data(using: .utf8) else { return }
        append(data)
    }

    mutating func addField(_ string: String) {
        append(string)
        append(.httpFieldDelimiter)
    }

    mutating func addField(_ data: Data) {
        append(data)
        append(.httpFieldDelimiter)
    }
}

fileprivate extension String {
    static let httpFieldDelimiter = "\r\n"
}

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