43

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 ?

Community
  • 1
  • 1
Amr Mohamed
  • 1,914
  • 4
  • 15
  • 37

8 Answers8

35

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
}
user3840170
  • 11,878
  • 11
  • 37
Anthony Akentiev
  • 951
  • 10
  • 9
26

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/

Dhaval Kansara
  • 1,697
  • 1
  • 11
  • 31
  • The final `\r\n` after the close-delimiter (`\r\n--\(boundary)--`) is optional. – Edward Brey Jun 28 '20 at 17:08
  • How to add extra json input along with this? – Rohit Feb 23 '21 at 16:05
  • 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 – Nazmul Hasan Mar 25 '21 at 06:41
5
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()

}
jansma
  • 1,455
  • 1
  • 13
  • 20
  • 1
    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 – Amr Mohamed Apr 14 '15 at 16:03
  • 2
    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 – jansma Apr 15 '15 at 08:16
  • @jansma Thank you for this comment about the "name" parameter! You helped me understand why a specific API was refusing my data. Cheers. – Eric Aya Sep 28 '16 at 21:14
  • @jansma, Can we add URL as a file name? – Lakshmi Yadav Oct 09 '20 at 08:16
  • @jansma, I want to send one url with the file. How can I send? – Lakshmi Yadav Oct 09 '20 at 08:16
1

The first thing I noticed is the application/octet-stream as Conten-Type, this is usually used when the file type is unknown. Some web frameworks/libraries will reject this content-type if an image is required.

Second, I can't see the post length anywhere, try to add it:

body.appendString("--\(boundary)--\r\n")

// set the content-length
request.setValue("\(body.length)", forHTTPHeaderField:"Content-Length")
bontoJR
  • 6,785
  • 1
  • 24
  • 39
  • 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 Apr 14 '15 at 15:27
  • 1
    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\" – bontoJR Apr 15 '15 at 07:56
1
class func postMultiPartdata( postdatadictionary: [AnyHashable: Any], apikey: String, completion: @escaping (Any) -> () ) {

        if Utils().isConnectedToNetwork() == false
        {
            Utils().showMessage("Check internet")
            return
        }

        let strURL = "http://redspark.biz/dropp/api/\(apikey)"

        let url = URL(string: strURL)
        var urlRequest = URLRequest(url: url!)

        urlRequest.httpMethod = "POST"

        let body = NSMutableData();
        let boundary = "---------------------------14737809831466499882746641449"
        let contentType = "multipart/form-data; boundary=\(boundary)"
        urlRequest.addValue(contentType, forHTTPHeaderField: "Content-Type")

        for (key, value) in postdatadictionary {

            if(value is Data)
            {
                let  TimeStamp = "\(Date().timeIntervalSince1970 * 1000)"

                body.append("--\(boundary)\r\n".data(using: .utf8)!)
                body.append("Content-Disposition: form-data; name=\"\(key)\"; filename=\"\(TimeStamp)\"\r\n".data(using:.utf8)!)
                body.append("field_mobileinfo_image\r\n".data(using: .utf8)!)

                body.append("--\(boundary)\r\n".data(using: .utf8)!)
                body.append("Content-Disposition: form-data; name=\"files[field_mobileinfo_image]\"; filename=\"img.jpg\"\r\n".data(using: .utf8)!)
                body.append("Content-Type: image/jpeg\r\n\r\n".data(using: .utf8)!)

//                var imgData: Data? = nil
//                if let aKey = value as? Data {
//                    imgData = NSData(data: aKey) as Data
//                }

                body.append(value as! Data)

            }
            else
            {
                if let anEncoding = "--\(boundary)\r\n".data(using: .utf8) {
                    body.append(anEncoding)
                }
                if let anEncoding = "Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8) {
                    body.append(anEncoding)
                }
                if let aKey = postdatadictionary[key], let anEncoding = "\(aKey)".data(using: .utf8) {
                    body.append(anEncoding)
                }
                if let anEncoding = "\r\n".data(using: .utf8) {
                    body.append(anEncoding)
                }
              }
            }

        if let anEncoding = "--\(boundary)--\r\n".data(using: .utf8) {
            body.append(anEncoding)
        }
        // setting the body of the post to the reqeust
        urlRequest.httpBody = body as Data

        URLSession.shared.dataTask(with:urlRequest) { (data, response, error) in
            if error != nil {

                print(error!)
                completion("")
            } else {
                var dictonary:NSDictionary?
                do {
                    dictonary = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary
                    if let myDictionary = dictonary
                    {
                        completion(myDictionary)
                    }
                } catch let error as NSError {
                    completion(error)
                }
            }

            Utils().HideLoader()

            }.resume()

        }
Ishwar Hingu
  • 496
  • 6
  • 14
1

following those answers here is my implementation with a concrete example for video and audio. notice that the boundary between elements has such form --boundary while the last boundary is written --boudary--.

    let url = URL(string: "https://...")!
    let boundary = UUID().uuidString
    var request = URLRequest(url: url)
    request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
    request.httpMethod = "post"
    var data = Data()
    data.addMultiPart(boundary: boundary, name: "metadata", filename: "metadata.json", contentType: "application/json", data: metaData)
    
    let ext = fileUrl.pathExtension.lowercased()
    let isImage = ["jpg","jpeg","png"].contains(ext)
    let contentType = isImage ? "image/\(ext)" : "video/\(ext)"
    let mediaData = try! Data(contentsOf: fileUrl)
    data.addMultiPart(boundary: boundary, name: "file", filename: fileUrl.lastPathComponent, contentType: contentType, data: mediaData)
    data.addMultiPartEnd(boundary: boundary)
    request.httpBody = data
    let task = session.dataTask(with: request)
    task.resume()


private extension Data {
mutating func addMultiPart(boundary: String, name: String, filename: String, contentType: String, data: Data) {
    print("adding boundary: \(boundary), name: \(name), filename: \(filename), contentType: \(contentType) data length: \(data.count) ")
    self.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
    self.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(filename)\"\r\n".data(using: .utf8)!)
    self.append("Content-Type: \(contentType)\r\n\r\n".data(using: .utf8)!)
    self.append(data)
}
mutating func addMultiPartEnd(boundary: String) {
    self.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
}

}

Nicolas Manzini
  • 7,809
  • 6
  • 58
  • 77
0

I implemented Upload image using Multi-part in Swift 4:

Here is the code. Please have a look

//MARK: Uplaod User Profile Pic
func uploadImageToServerFromApp(nameOfApi : NSString, parameters : NSString, uploadedImage : UIImage, withCurrentTask :RequestType, andDelegate :AnyObject)->Void {
    if self.isConnectedToNetwork(){
        currentTask = withCurrentTask
        let myRequestUrl = NSString(format: "%@%@%@",GlobalConstants.KBaseURL,nameOfApi,parameters)
        let url = (myRequestUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed))!
        var request : NSMutableURLRequest = NSMutableURLRequest()
        request = URLRequest(url: URL(string:url as String)!) as! NSMutableURLRequest
        request.httpMethod = "POST"
        let boundary = generateBoundaryString()
        //define the multipart request type
        request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
        let image_data = UIImagePNGRepresentation(uploadedImage)
        if(image_data == nil){
            return
        }
        let body = NSMutableData()
        let fname = "image.png"
        let mimetype = "image/png"
        //define the data post parameter
        body.append("--\(boundary)\r\n".data(using: String.Encoding.utf8)!)
        body.append("Content-Disposition:form-data; name=\"image\"\r\n\r\n".data(using: String.Encoding.utf8)!)
        body.append("hi\r\n".data(using: String.Encoding.utf8)!)
        body.append("--\(boundary)\r\n".data(using: String.Encoding.utf8)!)
        body.append("Content-Disposition:form-data; name=\"image\"; filename=\"\(fname)\"\r\n".data(using: String.Encoding.utf8)!)
        body.append("Content-Type: \(mimetype)\r\n\r\n".data(using: String.Encoding.utf8)!)
        body.append(image_data!)
        body.append("\r\n".data(using: String.Encoding.utf8)!)
        body.append("--\(boundary)--\r\n".data(using: String.Encoding.utf8)!)
        request.httpBody = body as Data
        let session = URLSession.shared
        let task = session.dataTask(with: request as URLRequest) { (data, response, error) in
            guard let data = data, error == nil else {                                                 // check for fundamental networking error
                // print("error=\(String(describing: error))")
                self.showAlertMessage(title: "App name", message: "Server not responding, please try later")
                return
            }
            if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {           // check for http errors
                // print("statusCode should be 200, but is \(httpStatus.statusCode)")
                // print("response = \(String(describing: response))")
                self.delegate?.internetConnectionFailedIssue()
            }else{
                do {
                    self.responseDictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! NSDictionary
                    // self.Responsedata = data as NSData
                    //self.responseDictionary = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! [String: AnyObject] as NSDictionary;

                    self.delegate?.responseReceived()
                } catch {
                    //print("error serializing JSON: \(error)")
                }
            }
        }
        task.resume()
    }
    else{
        // print("Internet Connection not Available!")
        self.showAlertMessage(title: "App Name", message: "No Internet Connection..")
    }
}

func generateBoundaryString() -> String
{
    return "Boundary-\(NSUUID().uuidString)"
}
Mannam Brahmam
  • 2,059
  • 2
  • 20
  • 35
0

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.

Amirhossein72
  • 361
  • 5
  • 7