Lana North
Lana North

Reputation: 139

SwiftUI, how to get progress whilst uploading multipart/form-data files using await urlSession.upload(...)

iOS 15+

How do I get the upload progress from the following using await urlSession.upload?

I specifically want the Await functionality which I dont seem to get (or understand how to) using urlSession.uploadTask.

The code uploads the file fine.

I've tried using the urlSession function at the bottom but it never fires.

https://developer.apple.com/documentation/foundation/urlsessiontaskdelegate/1408299-urlsession

Please note I normally code C# and I am mostly a beginner in swift.

Thank you.

NetworkManager.swift

import SwiftUI

class NetworkManager {
    
    static let shared = NetworkManager()
    
    private init() {}
    
    func uploadZipFile (
        zipFileURL: URL) async throws -> (Data, URLResponse) {
    
            let name:     String = zipFileURL.deletingPathExtension().lastPathComponent
            let fileName: String = zipFileURL.lastPathComponent
            
            let zipFileData: Data?
            
            do {
                zipFileData = try Data(contentsOf: zipFileURL)
            } catch {
                throw error
            }
            
            let uploadApiUrl: URL? = URL(string: "https://someapi.com/upload")
        
            // Generate a unique boundary string using a UUID.
            let uniqueBoundary = UUID().uuidString

            var bodyData = Data()
            
            // Add the multipart/form-data raw http body data.
            bodyData.append("\r\n--\(uniqueBoundary)\r\n".data(using: .utf8)!)
            bodyData.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!)
            bodyData.append("Content-Type: application/zip\r\n\r\n".data(using: .utf8)!)
            
            // Add the zip file data to the raw http body data.
            bodyData.append(zipFileData!)
    
            // End the multipart/form-data raw http body data.
            bodyData.append("\r\n--\(uniqueBoundary)--\r\n".data(using: .utf8)!)
            
            let urlSessionConfiguration = URLSessionConfiguration.default
            
            let urlSession
                = URLSession(
                    configuration: urlSessionConfiguration,
                    delegate: nil,                           // Something I need here maybe?
                    delegateQueue: nil)
            
            var urlRequest = URLRequest(url: uploadApiUrl!)
            
            // Set Content-Type Header to multipart/form-data with the unique boundary.
            urlRequest.setValue("multipart/form-data; boundary=\(uniqueBoundary)", forHTTPHeaderField: "Content-Type")
            
            urlRequest.httpMethod = "POST"
            
            let (data, urlResponse) = try await urlSession.upload(
                for: urlRequest,
                from: bodyData,
                delegate: nil   // Something I need here maybe?
            )

            return (data, urlResponse)
    }
    
    // Tried this but it never fires.
    func urlSession(
        _ session: URLSession,
        task: URLSessionTask,
        didSendBodyData bytesSent: Int64,
        totalBytesSent: Int64,
        totalBytesExpectedToSend: Int64) {
        
        print("fractionCompleted  : \(Float(totalBytesSent) / Float(totalBytesExpectedToSend))")
            
    }
}

Upvotes: 4

Views: 3649

Answers (1)

Lana North
Lana North

Reputation: 139

Answering my own question.

If anybody sees anything thats wrong or bad practice then please feel free to comment.

NetworkManager.swift

import SwiftUI

class NetworkManager: NSObject {
    
    static let shared = NetworkManager()
    
    private override init() {}
    
    func uploadZipFile (
        zipFileURL: URL) async throws -> (Data, URLResponse) {
    
            let name:     String = zipFileURL.deletingPathExtension().lastPathComponent
            let fileName: String = zipFileURL.lastPathComponent
            
            let zipFileData: Data?
            
            do {
                zipFileData = try Data(contentsOf: zipFileURL)
            } catch {
                throw error
            }
            
            let uploadApiUrl: URL? = URL(string: "https://someapi.com/upload")
        
            // Generate a unique boundary string using a UUID.
            let uniqueBoundary = UUID().uuidString

            var bodyData = Data()
            
            // Add the multipart/form-data raw http body data.
            bodyData.append("\r\n--\(uniqueBoundary)\r\n".data(using: .utf8)!)
            bodyData.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!)
            bodyData.append("Content-Type: application/zip\r\n\r\n".data(using: .utf8)!)
            
            // Add the zip file data to the raw http body data.
            bodyData.append(zipFileData!)
    
            // End the multipart/form-data raw http body data.
            bodyData.append("\r\n--\(uniqueBoundary)--\r\n".data(using: .utf8)!)
            
            let urlSessionConfiguration = URLSessionConfiguration.default
            
            let urlSession
                = URLSession(
                    configuration: urlSessionConfiguration,
                    delegate: self,         
                    delegateQueue: nil)
            
            var urlRequest = URLRequest(url: uploadApiUrl!)
            
            // Set Content-Type Header to multipart/form-data with the unique boundary.
            urlRequest.setValue("multipart/form-data; boundary=\(uniqueBoundary)", forHTTPHeaderField: "Content-Type")
            
            urlRequest.httpMethod = "POST"
            
            let (data, urlResponse) = try await urlSession.upload(
                for: urlRequest,
                from: bodyData,
                delegate: nil 
            )

            return (data, urlResponse)
    }
}

extension NetworkManager: URLSessionTaskDelegate {
    
    func urlSession(
        _ session: URLSession,
        task: URLSessionTask,
        didSendBodyData bytesSent: Int64,
        totalBytesSent: Int64,
        totalBytesExpectedToSend: Int64) {
        
        print("fractionCompleted  : \(Int(Float(totalBytesSent) / Float(totalBytesExpectedToSend) * 100))")
            
    }
    
}

Upvotes: 3

Related Questions