How to upload files with a multipart request in swift.

How to upload files with a multipart request in swift.

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.

Creating the MultipartRequest struct

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 the MultipartRequest struct.

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.

Conclusion.

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.