How to Upload Images Using multipart/form-data in Swift
In this article, we will explain how to POST an image file with Content-Type: multipart/form-data in Swift.
About the Data Format Posted with multipart/form-data
Before posting data with multipart/form-data in Swift, let's review what the multipart format looks like.
We will post item information from a simple form like the one below.
* The form action URL is a dummy. It will not actually post.
<form method="POST" enctype="multipart/form-data" action="http://xxx.com/Test/SaveItemMultipart">
<input name="itemID">
<input name="itemName">
<input type="file" name="imageFile">
<input type="submit" value="Submit">
</form>
On the server side, the received itemID, itemName, and image file are saved, and if successful, a response like this is returned:
<SaveItemResponse>
<Message/>
<Success>true</Success>
</SaveItemResponse>
If you capture the data when submitting the form using a tool like Wireshark, it looks like this:
The Content-Type is multipart/form-data.
A boundary such as ----WebKitFormBoundaryZwEFFOVFw0vcvPuS is defined and used to separate form data.
When the form data is a simple text key-value pair, after --boundary and a line break, there is Content-Disposition: form-data; name="keyName", followed by two line breaks, and then the value.
When the form data is a file, after --boundary and a line break, there is Content-Disposition: form-data; name="keyName"; followed by filename="fileName". Then a line break, Content-Type (in this case image/jpeg), another line break, and then the binary data of the image.
(Not shown in this image, but) after the image binary data, there is an ending --boundary-- line break.
This is the data that gets posted.
How to POST an Image File with multipart/form-data in Swift
To POST an image file with multipart/form-data in Swift, we will generate data in the format we confirmed above.
Here, we will only show the code for posting an image file with multipart/form-data.
We assume a JPEG image displayed in a UIImageView named imageView will be posted when the sendToServerTapped action is triggered.
@IBAction func sendToServerTapped(_ sender: Any) {
guard let image = imageView.image else { return }
guard let imageData = image.jpegData(compressionQuality: 1) else { return }
let itemID = 555
let itemName = "Item Name 555"
let imageFileName = "itemp555.jpg"
let boundary = "----------" + UUID().uuidString
var httpBody1 = "--\(boundary)\r\n"
httpBody1 += "Content-Disposition: form-data; name=\"ItemID\"\r\n"
httpBody1 += "\r\n"
httpBody1 += "\(itemID)\r\n"
httpBody1 += "--\(boundary)\r\n"
httpBody1 += "Content-Disposition: form-data; name=\"ItemName\"\r\n"
httpBody1 += "\r\n"
httpBody1 += "\(itemName)\r\n"
httpBody1 += "--\(boundary)\r\n"
httpBody1 += "Content-Disposition: form-data; name=\"imageFile\"; filename=\"\(imageFileName)\"\r\n"
httpBody1 += "Content-Type: image/jpeg\r\n"
httpBody1 += "\r\n"
var httpBody = Data()
httpBody.append(httpBody1.data(using: .utf8)!)
httpBody.append(imageData)
var httpBody2 = "\r\n"
httpBody2 += "--\(boundary)--\r\n"
httpBody.append(httpBody2.data(using: .utf8)!)
let url = URL(string: "http://xxx.com/Test/SaveItemMultipart")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.setValue("\(httpBody.count)", forHTTPHeaderField: "Content-Length")
request.httpBody = httpBody
URLSession.shared.dataTask(with: request) {(data, response, error) in
if let error = error {
if let err = error as? URLError, err.code == URLError.Code.notConnectedToInternet {
print("Internet connection not available. Please try again when internet connection is available.")
} else {
print("Unexpected error: \(error.localizedDescription).")
}
return;
}
if let response = response as? HTTPURLResponse {
if !(200...299).contains(response.statusCode) {
print("Request Failed - Status Code: \(response.statusCode).")
return
}
}
if let data = data {
do {
let jsonDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
let success = jsonDict!["Success"] as! Bool
let message = jsonDict!["Message"] as! String
if(success) {
print("success")
}
else {
print(message)
return
}
} catch {
print("Error parsing the response.")
}
} else {
print("Unexpected error.")
}
}.resume()
}
Let's go through the code that posts multipart/form-data step by step.
guard let image = imageView.image else { return }
guard let imageData = image.jpegData(compressionQuality: 1) else { return }
let itemID = 555
let itemName = "Item Name 555"
let imageFileName = "itemp555.jpg"
Line 2 assigns the UIImage from the UIImageView to image.
Line 3 converts the UIImage to JPEG Data using jpegData and assigns it to imageData.
Lines 5–7 hardcode the form data values and file name.
let boundary = "----------" + UUID().uuidString
var httpBody1 = "--\(boundary)\r\n"
httpBody1 += "Content-Disposition: form-data; name=\"ItemID\"\r\n"
httpBody1 += "\r\n"
httpBody1 += "\(itemID)\r\n"
httpBody1 += "--\(boundary)\r\n"
httpBody1 += "Content-Disposition: form-data; name=\"ItemName\"\r\n"
httpBody1 += "\r\n"
httpBody1 += "\(itemName)\r\n"
httpBody1 += "--\(boundary)\r\n"
httpBody1 += "Content-Disposition: form-data; name=\"imageFile\"; filename=\"\(imageFileName)\"\r\n"
httpBody1 += "Content-Type: image/jpeg\r\n"
httpBody1 += "\r\n"
Line 9 generates the boundary string using UUID.
We then separate parts with --boundary and add form data to httpBody1 as text.
Lines 12–14 add the ItemID form data in the format we confirmed earlier.
Lines 16–18 add the ItemName form data in the same way.
Lines 20–22 add the headers for the image file form data, up to just before the binary data.
var httpBody = Data()
httpBody.append(httpBody1.data(using: .utf8)!)
httpBody.append(imageData)
var httpBody2 = "\r\n"
httpBody2 += "--\(boundary)--\r\n"
httpBody.append(httpBody2.data(using: .utf8)!)
Lines 24–26 define a Data variable httpBody, append httpBody1 as Data, then append the JPEG imageData.
Lines 28–29 generate the trailing line breaks and the closing --boundary-- in httpBody2.
Line 31 appends httpBody2 as Data to httpBody.
Now httpBody contains the multipart-formatted data to be posted.
let url = URL(string: "http://xxx.com/Test/SaveItemMultipart")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.setValue("\(httpBody.count)", forHTTPHeaderField: "Content-Length")
request.httpBody = httpBody
Line 33 creates a URL object from the string.
Lines 34 - 38 configure the URLRequest.
Line 35 sets the httpMethod to POST.
Line 36 sets the Content-Type header to multipart/form-data with the boundary.
Line 37 sets the Content-Length header to httpBody.count (in bytes).
Line 38 assigns the generated httpBody to the request's httpBody.
URLSession.shared.dataTask(with: request) {(data, response, error) in
...
From line 40 onward, the request is posted using URLSession.shared.dataTask(with: request), and the response data is parsed to extract Success and Message.
If you'd like to learn more about sending HTTP requests (POST) using URLSession in Swift, see How to Make HTTP POST Requests with URLSession.
If successful, "success" will be printed in the console.
That's how to POST an image file with Content-Type: multipart/form-data in Swift.