Does anybody know how to upload an image using multipart/form-data POST method?

I need to upload an image via POST method using multipart/form-data

https://pixlab.io/cmd?id=facedetect

the image will be coming from the phone library

I know I can use Alamo fire for this https call but I don’t want to rely on third parties…I also found information online but I didn’t fully understand how to make the call.

if somebody has done it I’m even willing to pay somebody to write the function for me so I can save time.

thanks.

//MARK: - PIXLAB upload
func uploadImage(paramName: String, fileName: String, image: UIImage) {
    let url = URL(string: "https://api.pixlab.io/facedetect")

    // generate boundary string using a unique per-app string
    let boundary = UUID().uuidString

    let session = URLSession.shared

    // Set the URLRequest to POST and to the specified URL
    var urlRequest = URLRequest(url: url!)
    urlRequest.httpMethod = "POST"

    // Set Content-Type Header to multipart/form-data, this is equivalent to submitting form data with file upload in a web browser
    // And the boundary is also set here
    urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

    var data = Data()

    // Add the image data to the raw http request data
    
    //name = filePathKey == \local\etc
    //filename = "yourImageName.jpg"
    
    data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
    data.append("Content-Disposition: form-data; name=\"\(paramName)\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!)
    data.append("Content-Type: image/png\r\n\r\n".data(using: .utf8)!)
    data.append(image.pngData()!)

    data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)

    // Send a POST request to the URL, with the data we created earlier
    session.uploadTask(with: urlRequest, from: data, completionHandler: { responseData, response, error in
        if error == nil {
            let jsonData = try? JSONSerialization.jsonObject(with: responseData!, options: .allowFragments)
            if let json = jsonData as? [String: Any] {
                print(json)
            }
        }
    }).resume()
}

https://medium.com/@johnxavier034/uploading-array-of-images-using-multipart-form-data-in-swift-5d0cf8fc3361

https://orjpap.github.io/swift/http/ios/urlsession/2021/04/26/Multipart-Form-Requests.html

https://stackoverflow.com/questions/29623187/upload-image-with-multipart-form-data-ios-in-swift

1 Like

The boundary needs to be boundary=Boundary-UUID().uuidString

I think. Also you can use the variables you have but I think you’re missing the Boundary-UUIDString part

Let me know if that works or not, I have code that does this in a project, and I can put it in a gist for you

thanks Mikaela, I used the prefix you told me and also updated the code a bit (or stole it from stack overflow) and it worked

here is the code in case somebody is in the same situation:

func requestNativeImageUpload(image: UIImage) {

    let url = URL(string: "https://api.pixlab.io/facedetect")
    let boundary = "Boundary-\(NSUUID().uuidString)"
    var request = URLRequest(url: url!)

    let parameters = ["key" : "YOUR API KEY"]

    guard let mediaImage = Media(withImage: image, forKey: "file") else { return }

    request.httpMethod = "POST"

    request.allHTTPHeaderFields = [
                "X-User-Agent": "ios",
                "Accept-Language": "en",
                "Accept": "application/json",
                "Content-Type": "multipart/form-data; boundary=\(boundary)",
                "ApiKey": "YOUR API KEY"
            ]

    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 createDataBody(withParameters params: [String: String]?, 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/jpg"
        self.fileName = "\(arc4random()).jpeg"

        guard let data = image.jpegData(compressionQuality: 0.1) else { return nil }
        self.data = data
    }
}
2 Likes

this code is suddenly not working anymore, not sure if Xcode 13 is the culprit…

by any chance can you share your snippet @mikaelacaron , thanks

ignore. it was an issue with the endpoint itself. code was fine

2 Likes

I tried stuff for 3 days now, could not get anything from Stackoverflow to work.

This one finally does work now! Thank you @Naticio !

1 Like