SWIFT: Upload a file with multipart/form-data in iOS using uploadTask and URLSession

Let’s see how to upload any file to a web server, using only Apple’s built-in URLSession. Most of the examples in the Internet utilize dataTask and not the uploadTask. Also the examples I found demonstrate only the uploading of the image files. That’s why I decided to share a working code for building up a multipart/form-data request, that can be used in combination with uploadTask.

// generate boundary string using a unique string
let boundary = UUID().uuidString
        
// Set the URLRequest to POST and to the specified URL
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("Bearer \(yourAuthorizationToken)", forHTTPHeaderField: "Authorization")
        
// Content-Type is multipart/form-data, this is the same as submitting form data with file upload
// in a web browser
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
        
let fileName = fileURL.lastPathComponent
let mimetype = mimeType(for: fileName)
let paramName = "file"
let fileData = try? Data(contentsOf: fileURL)
var data = Data()
// Add the file data to the raw http request data
data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
data.append("Content-Disposition: form-data; name=\"\(paramName)\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!)
data.append("Content-Type: \(mimetype)\r\n\r\n".data(using: .utf8)!)
        data.append(fileData!)
        data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
// do not forget to set the content-length!
request.setValue(String(data.count), forHTTPHeaderField: "Content-Length")

As you can see, it’s pretty straightforward. First we create a request, setting Content-Type to “multipart/form-data” and Authorization token if required. You can set any other values if you web service requires. The second is to create the data that confirms to the multipart/form-data protocol. For we want to upload any kind of files, the mime type is recognized automatically:

private func mimeType(for path: String) -> String {
        let pathExtension = URL(fileURLWithPath: path).pathExtension as NSString
        guard
            let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension, nil)?.takeRetainedValue(),
            let mimetype = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue()
        else {
            return "application/octet-stream"
        }

        return mimetype as String
    }

So now we have two peaces of puzzle. To bring them together just use the uploadTask(with:data:) function:

session.uploadTask(with: request, from: data)

The webservice should be happy now with your upload file call.