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