Upload image with multipart form-data iOS in Swift

100,782

Solution 1

No Need to use any library for upload images using multipart request.

Swift 4.2

func uploadImage(paramName: String, fileName: String, image: UIImage) {
    let url = URL(string: "http://api-host-name/v1/api/uploadfile/single")

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

If you have any header to add, you can add it via urlRequest.setValue method.

Source: https://fluffy.es/upload-image-to-server/

Solution 2

My version that 100% works. Maybe it will help you.

let url = "http://server/upload"
let img = UIImage(contentsOfFile: fullPath)
let data: NSData = UIImageJPEGRepresentation(img, 1)

sendFile(url, 
    fileName:"one.jpg", 
    data:data, 
    completionHandler: completionHandler:{
        (result:Bool, isNoInternetConnection:Bool) -> Void in

            // ...     
            NSLog("Complete: \(result)")
    }
)


func sendFile(
    urlPath:String,
    fileName:String,
    data:NSData,
    completionHandler: (NSURLResponse!, NSData!, NSError!) -> Void){
        
        var url: NSURL = NSURL(string: urlPath)!
        var request1: NSMutableURLRequest = NSMutableURLRequest(URL: url)
        
        request1.HTTPMethod = "POST"
        
        let boundary = generateBoundary()
        let fullData = photoDataToFormData(data,boundary:boundary,fileName:fileName)
        
        request1.setValue("multipart/form-data; boundary=" + boundary,
            forHTTPHeaderField: "Content-Type")
        
        // REQUIRED!
        request1.setValue(String(fullData.length), forHTTPHeaderField: "Content-Length")
        
        request1.HTTPBody = fullData
        request1.HTTPShouldHandleCookies = false
        
        let queue:NSOperationQueue = NSOperationQueue()
        
        NSURLConnection.sendAsynchronousRequest(
            request1,
            queue: queue,
            completionHandler:completionHandler)
}

// this is a very verbose version of that function
// you can shorten it, but i left it as-is for clarity
// and as an example
func photoDataToFormData(data:NSData,boundary:String,fileName:String) -> NSData {
    var fullData = NSMutableData()
    
    // 1 - Boundary should start with --
    let lineOne = "--" + boundary + "\r\n"
    fullData.appendData(lineOne.dataUsingEncoding(
        NSUTF8StringEncoding,
        allowLossyConversion: false)!)
    
    // 2
    let lineTwo = "Content-Disposition: form-data; name=\"image\"; filename=\"" + fileName + "\"\r\n"
    NSLog(lineTwo)
    fullData.appendData(lineTwo.dataUsingEncoding(
        NSUTF8StringEncoding,
        allowLossyConversion: false)!)
    
    // 3
    let lineThree = "Content-Type: image/jpeg\r\n\r\n"
    fullData.appendData(lineThree.dataUsingEncoding(
        NSUTF8StringEncoding,
        allowLossyConversion: false)!)
    
    // 4
    fullData.appendData(data)
    
    // 5
    let lineFive = "\r\n"
    fullData.appendData(lineFive.dataUsingEncoding(
        NSUTF8StringEncoding,
        allowLossyConversion: false)!)
    
    // 6 - The end. Notice -- at the start and at the end
    let lineSix = "--" + boundary + "--\r\n"
    fullData.appendData(lineSix.dataUsingEncoding(
        NSUTF8StringEncoding,
        allowLossyConversion: false)!)
    
    return fullData
}

Solution 3

public func UPLOADIMG(url: String,parameters: Dictionary<String,AnyObject>?,filename:String,image:UIImage, success:((NSDictionary) -> Void)!, failed:((NSDictionary) -> Void)!, errord:((NSError) -> Void)!) {
        var TWITTERFON_FORM_BOUNDARY:String = "AaB03x"
        let url = NSURL(string: url)!
        var request:NSMutableURLRequest = NSMutableURLRequest(URL: url, cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData, timeoutInterval: 10)
        var MPboundary:String = "--\(TWITTERFON_FORM_BOUNDARY)"
        var endMPboundary:String = "\(MPboundary)--"
        //convert UIImage to NSData            
        var data:NSData = UIImagePNGRepresentation(image)
        var body:NSMutableString = NSMutableString();
        // with other params
        if parameters != nil {
            for (key, value) in parameters! {
                body.appendFormat("\(MPboundary)\r\n")
                body.appendFormat("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
                body.appendFormat("\(value)\r\n")
            }
        }
        // set upload image, name is the key of image 
        body.appendFormat("%@\r\n",MPboundary)
        body.appendFormat("Content-Disposition: form-data; name=\"\(filename)\"; filename=\"pen111.png\"\r\n")
        body.appendFormat("Content-Type: image/png\r\n\r\n")
        var end:String = "\r\n\(endMPboundary)"
        var myRequestData:NSMutableData = NSMutableData();
        myRequestData.appendData(body.dataUsingEncoding(NSUTF8StringEncoding)!)
        myRequestData.appendData(data)
        myRequestData.appendData(end.dataUsingEncoding(NSUTF8StringEncoding)!)
        var content:String = "multipart/form-data; boundary=\(TWITTERFON_FORM_BOUNDARY)"
        request.setValue(content, forHTTPHeaderField: "Content-Type")
        request.setValue("\(myRequestData.length)", forHTTPHeaderField: "Content-Length")
        request.HTTPBody = myRequestData
        request.HTTPMethod = "POST"
        //        var conn:NSURLConnection = NSURLConnection(request: request, delegate: self)!
        let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {
            data, response, error in
            if error != nil {
                println(error)
                errord(error)
                return
            }
            var parseError: NSError?
            let responseObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &parseError)
            if let responseDictionary = responseObject as? NSDictionary {
                success(responseDictionary)
            } else {
            }

        })
        task.resume()

}

Solution 4

import Foundation

struct MultipartFormDataRequest {
    private let boundary: String = UUID().uuidString
    var httpBody = NSMutableData()
    let url: URL
    
    init(url: URL) {
        self.url = url
    }
    
    func addTextField(named name: String, value: String) {
        httpBody.appendString(textFormField(named: name, value: value))
    }
    
    private func textFormField(named name: String, value: String) -> String {
        var fieldString = "--\(boundary)\r\n"
        fieldString += "Content-Disposition: form-data; name=\"\(name)\"\r\n"
        fieldString += "Content-Type: text/plain; charset=ISO-8859-1\r\n"
        fieldString += "Content-Transfer-Encoding: 8bit\r\n"
        fieldString += "\r\n"
        fieldString += "\(value)\r\n"
        
        return fieldString
    }
    
    
    func addDataField(fieldName: String, fileName: String, data: Data, mimeType: String) {
        httpBody.append(dataFormField(fieldName: fieldName,fileName:fileName,data: data, mimeType: mimeType))
    }
    
    private func dataFormField(fieldName: String,
                               fileName: String,
                               data: Data,
                               mimeType: String) -> Data {
        let fieldData = NSMutableData()
        
        fieldData.appendString("--\(boundary)\r\n")
        fieldData.appendString("Content-Disposition: form-data; name=\"\(fieldName)\"; filename=\"\(fileName)\"\r\n")
        fieldData.appendString("Content-Type: \(mimeType)\r\n")
        fieldData.appendString("\r\n")
        fieldData.append(data)
        fieldData.appendString("\r\n")
        return fieldData as Data
    }
    
    func asURLRequest() -> URLRequest {
        var request = URLRequest(url: url)
        
        request.httpMethod = "POST"
        request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
        
        httpBody.appendString("--\(boundary)--")
        request.httpBody = httpBody as Data
        return request
    }
}

extension NSMutableData {
    func appendString(_ string: String) {
        if let data = string.data(using: .utf8) {
            self.append(data)
        }
    }
}


extension URLSession {
    func dataTask(with request: MultipartFormDataRequest,
                  completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void)
    -> URLSessionDataTask {
        return dataTask(with: request.asURLRequest(), completionHandler: completionHandler)
    }
}

Use this function to call upload file func

func uploadFile(file:Data, fileName: String, fileExtension: String){
    var mimeType = "image/png"
    if fileExtension == "PDF" {
        mimeType = "application/pdf"
    }
    let url = "https://v2.convertapi.com/upload"

    let request = MultipartFormDataRequest(url: URL(string: url)!)
    request.addDataField(fieldName:  "file", fileName: fileName, data: file, mimeType: mimeType)
    URLSession.shared.dataTask(with: request, completionHandler: {data,urlResponse,error in
        

    }).resume()
}

#Resources:

https://www.donnywals.com/uploading-images-and-forms-to-a-server-using-urlsession/ https://orjpap.github.io/swift/http/ios/urlsession/2021/04/26/Multipart-Form-Requests.html

Solution 5

I suggest this repository. You can add pod 'MultipartForm' in the Podfile and then follow the example in the repository's readme:

import MultipartForm

let form = MultipartForm(parts: [
MultipartForm.Part(name: "a", value: "1"),
MultipartForm.Part(name: "b", value: "2"),
MultipartForm.Part(name: "c", data: imageData, filename: "3.png", contentType: "image/png"),
])

var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(form.contentType, forHTTPHeaderField: "Content-Type")

let task = session.uploadTask(with: request, from: form.bodyData)
task.resume()

It supports Swift Package Manager too.

Share:
100,782
Amr Mohamed
Author by

Amr Mohamed

iOS software Engineer with 5 years experience in crafting the best user interface, I am constantly passionate about learning new technologies and always looking forward to make my self better. I keep reading books every day about iOS Development and software engineering, I learned most of my experience from reading raywenderlich books and also from reading Apple programming guidelines .

Updated on July 09, 2022

Comments

  • Amr Mohamed
    Amr Mohamed almost 2 years

    i am having a problem with uploading image with multipart-form

    here is my code i used from this answer

        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
    

    then i use NSURLSession to apply the request

    the server says that i didn't choose image to upload i only want to upload the image for now

    do i have to use paths of images to upload any image or it's data is enough?

    do i miss any thing , any help to understand this ?

  • Amr Mohamed
    Amr Mohamed about 9 years
    Sorry for late reply and thanks for trying to help , i updated the code with content-Type and the Content-Length but still got the same error , do you recommend anything else ? and also something that i don't understand is there any key i use from the php code like form-name or something else , is this line ok body.appendString("Content-Disposition: form-data; name=\"image\"; filename=\"image.png\"\r\n") ?
  • Amr Mohamed
    Amr Mohamed about 9 years
    same error , do you have any other explanation ? , and what is this name and filename here in this line body.appendFormat("Content-Disposition: form-data; name=\"\(filename)\"; filename=\"pen111.png\"\r\n") , is there any key i use from the php code like form-name or something else in the same line , thanks
  • bontoJR
    bontoJR about 9 years
    Yes, name=\"image\" is actually containing the field name you are uploading the image on. So, if in your form you have the image field named 'image-field', you have to write name=\"image-field\"
  • jansma
    jansma about 9 years
    name is the key of image ,like the attribute called name in input tag(html),it is important, if you get the upload file in php use $photo = $_FILES['file'](thinkphp); you must set the name file ; filename is the image file name,like "aaaa.png" you can change the name by yourself @Panda
  • ajjnix
    ajjnix about 8 years
    thanks you for with answer!) I just add in solution request1.setValue("gzip, deflate", forHTTPHeaderField: "Accept-Encoding") and my image did uploaded
  • Eric Aya
    Eric Aya over 7 years
    @jansma Thank you for this comment about the "name" parameter! You helped me understand why a specific API was refusing my data. Cheers.
  • Fattie
    Fattie about 7 years
    Wow. Something I'm thinking, what if you ALSO have to send text input fields?
  • Priyal
    Priyal about 6 years
    What is generateBoundary?
  • Debaprio B
    Debaprio B about 6 years
    Don't you need to set the 'Content-Length' in the headers?
  • Mannam Brahmam
    Mannam Brahmam about 6 years
    You can add dear,,. body.setValue("(myRequestData.length)", forHTTPHeaderField: "Content-Length")
  • Debaprio B
    Debaprio B about 6 years
    Yeah. We must add the “Content-Length”, otherwise multipart doesn’t work 🙂
  • aidinMC
    aidinMC over 5 years
  • emmics
    emmics over 4 years
    For future cases: This is done so much easier with frameworks like Moya.
  • Edward Brey
    Edward Brey almost 4 years
    The final \r\n after the close-delimiter (\r\n--\(boundary)--) is optional.
  • Lakshmi Yadav
    Lakshmi Yadav over 3 years
    @Anthony, I am able to send file to server. Now I want to send some description too with the file. Any suggestions?
  • Lakshmi Yadav
    Lakshmi Yadav over 3 years
    @jansma, Can we add URL as a file name?
  • Lakshmi Yadav
    Lakshmi Yadav over 3 years
    @jansma, I want to send one url with the file. How can I send?
  • Rohit
    Rohit about 3 years
    How to add extra json input along with this?
  • Nazmul Hasan
    Nazmul Hasan about 3 years
    I change it from data.append("Content-Type: image/png\r\n\r\n".data(using: .utf8)!) to data.append("Content-Type: \"content-type header\"\r\n\r\n".data(using: .utf8)!) for my end to workable this coe
  • Abrcd18
    Abrcd18 over 2 years
    Hi! Could you please say paramName is base64String?
  • Farras Doko
    Farras Doko over 2 years
    so filename is required, or the upload will be ignored. The filename can be filled with whatever (not the actual filename).