Home > Software engineering >  How to make multiForm data post request in swift?
How to make multiForm data post request in swift?

Time:06-24

I need to send a form data in a post request to a backend URL. The form includes strings, Int values and images. So I followed a blog post and wrote some code for it.

I first created a struct along with 3 extensions:

struct MultipartFormDataRequest {
    private let boundary: String = UUID().uuidString
    private var httpBody = NSMutableData()
    let url: URL


    init(url: URL) {
        self.url = url
    }

    func addTextField(named name: String, value: String) {
        httpBody.append(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(named name: String, data: Data, mimeType: String) {
        httpBody.append(dataFormField(named: name, data: data, mimeType: mimeType))
    }

    private func dataFormField(named name: String,
                               data: Data,
                               mimeType: String) -> Data {
        let fieldData = NSMutableData()

        fieldData.append("--\(boundary)\r\n")
        fieldData.append("Content-Disposition: form-data; name=\"\(name)\"\r\n")
        fieldData.append("Content-Type: \(mimeType)\r\n")
        fieldData.append("\r\n")
        fieldData.append(data)
        fieldData.append("\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 append(_ string: String) {
        if let data = string.data(using: .utf8) {
            self.append(data)
        }
    }
}

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

In my app i have 2 textfields linked with @Published wrapper. The int values are just constant 1 for now. Also there's an image that the user selects with an image picker and then this is how i upload.

 func uploadForm() {
        //myInt is a variable  var myInt = 1, that's it
        var sum = CreateAccountAuth.shared.userDetails?.id
        let myIntData = Data(bytes: &myInt,
                             count: MemoryLayout.size(ofValue: myInt))
        let myIntData2 = Data(bytes: &sum, count: MemoryLayout.size(ofValue: sum))
        let request = MultipartFormDataRequest(url: URL(string: "my backend url")!)
        request.addDataField(named: "product_image1", data: (self.image?.pngData())!, mimeType: "img/jpeg")
        request.addTextField(named: "productName", value: self.artworkTitle)
        request.addTextField(named: "producDescription", value: self.artworkDescription)
        request.addDataField(named: "metauserID", data: myIntData2, mimeType: "Int")
        request.addDataField(named: "product_categoryID", data: myIntData, mimeType: "Int")
        request.addDataField(named: "discount_ID", data: myIntData, mimeType: "Int")
        request.addDataField(named: "shop_ID", data: myIntData, mimeType: "Int")
        request.addDataField(named: "boostTagsID", data: myIntData, mimeType: "Int")
        let newRequest = request.asURLRequest()
        let task =  URLSession.shared.dataTask(with: newRequest) { data, value, error in
            guard let data = data, error == nil else {
                return
            }
            do {
                guard let value = value as? HTTPURLResponse else {return}
                if value.statusCode >= 200 && value.statusCode < 300 {
                    print("good")
                }
                else if value.statusCode >= 300 {
                    print("bad")
                }
                let response = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
                print(response)
                
            } catch  {
                print(error)
            }
        }
        task.resume()
        
    }
}

When i trigger this function through a button after adding some text and an image I get back error saying:

bad Error Domain=NSCocoaErrorDomain Code=3840 "Unable to parse empty data." UserInfo={NSDebugDescription=Unable to parse empty data.}

I tried using alamofire as well but couldn't find useful blogs on uploading data other than string.

CodePudding user response:

I figured it out,

first of all, I should have checked if

var sum = CreateAccountAuth.shared.userDetails?.id

is nil or not and it turns out it is nil, I would use UserDefaults to store that Int value from now on to persist it

second, the form takes in both Int and String as Text which means the code I wrote for converting Int to data is not needed simply writing it using string interpolation

 "\(Integer)"

and third of all whenever sending an image we need to give it a filename, my code doesn't have it so it was failing

so here's the revised code, I am using Alamofire now since it is much easier

func uploadPost() {

guard let imageData = self.image?.jpegData(compressionQuality: 1)  else {
    print("the image data is empty")
    return}

AF.upload(multipartFormData: { (multipartdata) in
    multipartdata.append("\(2)".data(using: String.Encoding.utf8)!, withName: "metauserID")
    multipartdata.append("\(1)".data(using: String.Encoding.utf8)!, withName: "boostTagsID")
    multipartdata.append("\(1)".data(using: String.Encoding.utf8)!, withName: "shop_ID")
    multipartdata.append("\(1)".data(using: String.Encoding.utf8)!, withName: "discount_ID")
    multipartdata.append("\(1)".data(using: String.Encoding.utf8)!, withName: "product_categoryID")
    multipartdata.append(self.artworkDescription.data(using: String.Encoding.utf8)!, withName: "producDescription")
    multipartdata.append(self.artworkTitle.data(using: String.Encoding.utf8)!, withName: "productName")
 
    multipartdata.append(imageData, withName: "product_image1", fileName: "\(UUID().uuidString)   .png", mimeType: "img/jpeg")
    //multipartdata.append("\()", withName: "metauderID")
}, to: "my Backend URL").responseJSON { (data) in
    print(data)
}
    
}
  • Related