How to upload files with a multipart request in swift.

Search for a command to run...

No comments yet. Be the first to comment.
If you have a Mac and you deploy your applications with docker, there is the possibility you get this error The requested image's platform (Linux/arm64/v8) does not match the detected host platform (linux/amd64/v4) and no specific platform was reques...

One of the first things I wondered when I started working with Kubernetes was; how to deploy images from a private registry. Most examples on tutorials, use a public image from docker hub to show you how to create a deployment, but when you work in a...

Recently I had to make a multipart request from an iOS app written in Swift.
The thing is, that there is not a standard way to make it easily, at least not without any third-party library. So, making some research I found this blog post.
The author explains really well how to build a multipart request through a self-written struct called MultipartRequest.
But in my case, it didn't work out of the box, I'm pretty sure it was because I'm using a newer version of Swift.
So, in order to make it work, I made a few changes.
The principal change I made was to replace the type of the data property from Data to NSMutableData.
private var data: NSMutableData
This is because the compiler marked an error telling me that Data was not mutable.
Then I had to add an extension to NSMutableData instead to Data.
extension NSMutableData {
func appendString(_ string: String) {
if let data = string.data(using: .utf8) {
self.append(data)
}
}
}
And finally modified the calculated property httpBody to cast the NSMutableData to Data.
public var httpBody: Data {
data.appendString("--\(boundary)--")
return data as Data
}
Here you can see the full struct:
import Foundation
public struct MultipartRequest {
public let boundary: String
private let separator: String = "\r\n"
private var data: NSMutableData
public init(boundary: String = UUID().uuidString) {
self.boundary = boundary
self.data = .init()
}
private mutating func appendBoundarySeparator() {
data.appendString("--\(boundary)\(separator)")
}
private mutating func appendSeparator() {
data.appendString(separator)
}
private func disposition(_ key: String) -> String {
"Content-Disposition: form-data; name=\"\(key)\""
}
public mutating func add(
key: String,
value: String
) {
appendBoundarySeparator()
data.appendString(disposition(key) + separator)
appendSeparator()
data.appendString(value + separator)
}
public mutating func add(
key: String,
fileName: String,
fileMimeType: String,
fileData: Data
) {
appendBoundarySeparator()
data.appendString(disposition(key) + "; filename=\"\(fileName)\"" + separator)
data.appendString("Content-Type: \(fileMimeType)" + separator + separator)
data.append(fileData)
appendSeparator()
}
public var httpContentTypeHeadeValue: String {
"multipart/form-data; boundary=\(boundary)"
}
public var httpBody: Data {
data.appendString("--\(boundary)--")
return data as Data
}
}
extension NSMutableData {
func appendString(_ string: String) {
if let data = string.data(using: .utf8) {
self.append(data)
}
}
}
Using this struct is very easy:
First, instantiate an object of the MultipartRequest struct.
var multipart = MultipartRequest()
Now you can add parameters to your request:
multipart.add(key: "name", value: "John")
And of course, you can add your file data (in this example it's an image):
multipart.add(
key: "file",
fileName: "test.png",
fileMimeType: "image/png",
fileData: imageData
)
Build your request.
let url = URL(string: "https://myurl")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
// Here is how to use your struct to set your content-type header
request.setValue(multipart.httpContentTypeHeadeValue, forHTTPHeaderField: "Content-Type")
// set the body request
request.httpBody = multipart.httpBody
Finally, execute the request.
do {
let (responseData, response) = try await URLSession.shared.data(for: request)
print((response as! HTTPURLResponse).statusCode)
print(String(data: responseData, encoding: .utf8)!)
} catch {
print ("Error")
}
Now you can upload files from your iOS app.
I created this Github repository as an example using an image picker on SwiftUI.
Share in the comments if it is useful or if you know another way to do it.
Thanks for reading.