Upload SDK for iOS

The FastPix iOS Upload SDK helps you to upload videos from your iOS app to the FastPix platform. It’s built to handle large files, manage network interruptions, and give you full control over the upload process all with minimal setup.




Why use the FastPix iOS upload SDK?

The FastPix iOS SDK is built for reliability, flexibility, and performance when uploading large video files from your app. Key features include:

  • Chunked uploads: Automatically breaks large videos into smaller chunks for smoother transfers. You can customize the chunkSize to fit your app’s needs.
  • Resumable uploads: If the network drops or the app closes, uploads resume from where they left off no need to start over.
  • Pause and resume support: Give users control by allowing uploads to be paused mid-way and resumed later without data loss.


Step 1: Install the SDK

Prerequisites

  • An existing Xcode project.
  • Swift Package Manager (SPM) installed on your development machine.

Installing using Swift Package Manager (SPM)

  1. Open your Xcode project.
  2. Navigate to the "File" menu and select "Swift Packages" > "Add Package Dependency..."
  3. In the search bar, enter the URL for the FastPixUploadSDK repository: https://github.com/FastPix/iOS-Uploads
  4. Locate the desired version of the SDK. By default, the latest version will be displayed.
  5. Click "Add Package" to integrate the FastPixUploadSDK into your project.

Importing the SDK package

Once the package dependency is added, you can import the FastPixUploadSDK module:

import fp_swift_upload_sdk 


Step 2: Create an upload URL

To fully integrate the FastPixUploadSDK into your iOS application, follow these steps:


Generating a signed direct upload URL

  • Implement server-side logic to interact with FastPix's API to create a signed URL.
  • You will need an access token from FastPix (Token ID and Secret Key), generated in your FastPix dashboard.
  • After making the request, FastPix returns a signed URL used for secure uploads.

func createDirectUpload() async throws -> URL {
    
    let parameters: [String: Any] = [
        "corsOrigin": "*",
        "newAssetSettings": [
            "accessPolicy": "public",
            "generateSubtitles": true,
            "normalizeAudio": true,
            "maxResolution": "1080p"
        ]
    ]
    
    guard let jsonData = try? JSONSerialization.data(withJSONObject: parameters) else {
        throw NSError(domain: "com.example.app", code: 500, userInfo: [
            NSLocalizedDescriptionKey: "Failed to serialize parameters"
        ])
    }
    
    let request = try {
        var req = try URLRequest(url: fullURL(forEndpoint: "upload"))
        req.httpBody = jsonData
        req.httpMethod = "POST"
        req.addValue("application/json", forHTTPHeaderField: "Content-Type")
        req.addValue("application/json", forHTTPHeaderField: "Accept")
        
        let credentials = "\(FASTPIX_ACCESS_TOKEN_ID):\(FASTPIX_SECRET_KEY)"
        let basicAuthCredential = Data(credentials.utf8).base64EncodedString()
        req.addValue("Basic \(basicAuthCredential)", forHTTPHeaderField: "Authorization")
        
        return req
    }()
    
    let (data, response) = try await urlSession.data(for: request)
    guard let httpResponse = response as? HTTPURLResponse else {
        throw CreateUploadError(message: "Invalid HTTP response")
    }
    
    if (200...299).contains(httpResponse.statusCode) {
        do {
            if let jsonDictionary = try JSONSerialization.jsonObject(with: data) as? [String: Any],
               let dataDic = jsonDictionary["data"] as? [String: Any],
               let urlString = dataDic["url"] as? String,
               let uploadUrl = URL(string: urlString) {
                return uploadUrl
            } else {
                print("Failed to parse upload URL from response.")
                return URL(string: "")!
            }
        } catch {
            print("Error decoding response: \(error.localizedDescription)")
            return URL(string: "")!
        }
    } else {
        let errorMessage = String(decoding: data, as: UTF8.self)
        throw CreateUploadError(message: "Upload POST failed: HTTP \(httpResponse.statusCode):\n\(errorMessage)")
    }
}

/// Generates a full URL for a given endpoint in the FastPix Video public API
private func fullURL(forEndpoint endpoint: String) throws -> URL {
    let fullPath = "https://api.fastpix.io/v1/on-demand/\(endpoint)"
    guard let url = URL(string: fullPath) else {
        throw CreateUploadError(message: "Bad endpoint: \(endpoint)")
    }
    return url
}



Step 3: Start your upload

import fp_swift_upload_sdk

// Initialize the uploader
var uploader = Uploads()

Task {
    do {
        // Get the upload URL from your backend
        let createUploadURL = try await self.myServerBackend.createDirectUpload()
        
        // Start the upload
        uploader.uploadFile(
            file: file!,
            endpoint: createUploadURL.absoluteString,
            chunkSizeKB: Int(chunkSize.text ?? "") ?? 0
        )
        
    } catch {
        print("Failed to create upload URL: \(error.localizedDescription)")
    }
}

  • Uploader initialization: var uploader = Uploads() prepares for a new session.
  • Async upload trigger: Runs inside Task for async handling.
  • Signed URL retrieval: Calls createDirectUpload() from your backend.
  • File upload start: Uses uploadFile() to initiate upload.

Add this code in your upload functionality module within your iOS app project. It could be in a service layer or directly within a controller or view model where the upload operation is triggered, ensuring it is properly integrated with your app's workflow.



Step 4: Track progress with progressHandler

You can show real-time progress updates during the upload by using the progressHandler callback provided by the SDK. This allows you to update progress bars, display percentage completion, and notify users when the upload finishes.


import fp_swift_upload_sdk

var uploader = Uploads()

uploader.progressHandler = { [weak self] progress in
    // Ensure UI updates are done on the main thread
    DispatchQueue.main.async {
        guard let self = self else { return }

        // Show and style progress UI
        self.progressBar.layer.cornerRadius = 10
        self.progressBar.isHidden = false
        self.label_progress.isHidden = false

        // Update progress bar and label
        self.progressBar.progress = progress
        self.label_progress.text = String(format: "%.0f%%", progress * 100)

        // If upload is complete
        if progress == 1.0 {
            self.button_pause.isHidden = true
            self.button_abort.isHidden = true
            self.button_upload.isHidden = false

            // Show success alert
            let alertController = UIAlertController(
                title: "Success",
                message: "The video file uploaded successfully.",
                preferredStyle: .alert
            )
            let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
            alertController.addAction(okAction)
            self.present(alertController, animated: true, completion: nil)
        }
    }
}


Step 5 : Pause and resume uploads

Pause and resumable uploads allow you to temporarily pause a file upload and then pick it up later from where it left off. This is especially useful for large video files that may be interrupted due to network issues or app crashes.


  • Create an uploader object
    var uploader = Uploads()

  • Call pause() to temporarily stop the upload
    uploader.pause()

  • Call resume() to continue the upload from where it stopped
    uploader.resume()

Below is an example implementation of the iOS Upload SDK. You can customize the UI and components based on your app’s design.


import UIKit 
import PhotosUI 
import fp_swift_upload_sdk 
import Foundation 
import Network 
 
class SelectUploadVideoViewController: UIViewController, PHPickerViewControllerDelegate, UITextFieldDelegate, UploadProgressDelegate, UploadSDKErrorDelegate, UIDocumentPickerDelegate { 
     
 
    @IBOutlet weak var thumbNailImage: UIImageView! 
    @IBOutlet weak var button_upload: UIButton! 
    @IBOutlet weak var progressBar: UIProgressView! 
    @IBOutlet weak var label_progress: UILabel! 
    @IBOutlet weak var button_selectVideo: UIButton! 
    @IBOutlet weak var errorLabel: UILabel! 
    @IBOutlet weak var buttonsStack: UIStackView! 
    @IBOutlet weak var button_pause: UIButton! 
    @IBOutlet weak var chunkSize: UITextField! 
    @IBOutlet weak var uploadedChunk: UILabel! 
    @IBOutlet var button_abort: UIButton! 
     
    var isOfflineModeEnabled = false 
    var uploadIsPaused = false 
    var file : URL? 
    var pauseBtnClicked = false 
    var uploader = Uploads() 
    var picje = VideoPickerConfiguration() 
    var thumGenerater = ThumbnailImageGenerater() 
    var authenticatedURL: URL? = nil 
    var videoInputURL: URL? = nil 

    let myServerBackend = UploadURLGenerater(urlSession: URLSession(configuration: URLSessionConfiguration.default)) 
    private var prepareTask: Task<Void, Never>? = nil 
    public var isPaused = false 
    private var assetRequestId: PHImageRequestID? = nil 

    let session = URLSession.shared 
    var requestData  = [String: Any]() 
    var chunks : Int = 0 
    var resData = [String]() 
    var urlIndex = 0 
    var chunksList: [String] = [] 
    var chunksArray: [Data] = [] 
    var signedURL: URL? 
    var objectName: String? 
    var uploadId: String? 
    let monitor = NWPathMonitor() 
    var isInternetAvailable = false 
    var chunkAttempt = 0 
     
    override func viewDidLoad() { 
        super.viewDidLoad() 
        chunkSize.delegate = self 
        uploader.progressDelegate = self 
        uploader.errorDelegate = self 
        button_abort.isHidden = true 
        pickerConfig() 
        title = "Create New Upload" 
        button_upload.isHidden = false 
        button_pause.isHidden = true 
        PHPhotoLibrary.authorizationStatus(for: .readWrite) 
        button_upload.isEnabled = false 
        button_upload.backgroundColor = .lightGray 

        // Set the progress handler 
        progressBar.isHidden = true 
        label_progress.isHidden = true 
        uploader.progressHandler = { [weak self] progress in 

            // Update UI with the progress value 
            DispatchQueue.main.async { 
                self?.progressBar.layer.cornerRadius = 10 
                self?.progressBar.isHidden = false 
                self?.label_progress.isHidden = false 

                // Update progress bar or any other UI element 
                self?.progressBar.progress = progress 
                self?.label_progress.text = String(format: "%.0f%%", progress * 100) 
                if progress == 1.0 { 
                    self?.button_pause.isHidden = true 
                    self?.button_abort.isHidden = true 
                    self?.button_upload.isHidden = false 
                    let alertController = UIAlertController(title: "success", message: "The video file uploaded successfully.", preferredStyle: .alert) 
                    let okAction = UIAlertAction(title: "OK", style: .default, handler: nil) 
                    alertController.addAction(okAction) 
                    self?.present(alertController, animated: true, completion: nil) 
                } 
            } 
        } 
      
        startMonitoring() 
         
        NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) 
         
        chunkSize.attributedPlaceholder = NSAttributedString( 
            string: "Enter chunk size", 
            attributes: [ 
                .foregroundColor: UIColor.white // Change to your desired color 
            ] 
        ) 
    } 
     
    func didUpdateProgressText(_ text: String) { 
        DispatchQueue.main.async { 
            self.uploadedChunk.text = text 
        } 
    } 
     
    func uploadSDKDidFail(with error: String) { 
        DispatchQueue.main.async { 
            self.displayErrorAlert(error: error) 
        } 
    } 
     
    func textFieldShouldReturn(_ textField: UITextField) -> Bool { 
        textField.resignFirstResponder() // Dismisses the keyboard 
            return true 
    } 
     
    func startMonitoring() { 
        monitor.pathUpdateHandler = { path in 
            DispatchQueue.main.async { [self] in 
                self.isInternetAvailable = path.status == .satisfied 
                if self.isInternetAvailable { 
                    if uploadIsPaused { 
                        if self.chunkAttempt < chunksArray.count { 

                        } 
                    } 
                } else { 
                } 
            } 
        } 
        let queue = DispatchQueue(label: "NetworkMonitor") 
        monitor.start(queue: queue) 
    } 
     
    @objc func appDidBecomeActive() { 
        startMonitoring() 
    } 
     
    func pickerConfig() { 
        let alert = UIAlertController(title: "Select Media Type", message: nil, preferredStyle: .actionSheet) 
 
        // Option to pick Video 
        alert.addAction(UIAlertAction(title: "Pick Video", style: .default, handler: { _ in 
            let config = self.picje.pickerConfig() // video-only config 
            let picker = PHPickerViewController(configuration: config) 
            picker.delegate = self 
            picker.modalPresentationStyle = .overCurrentContext 
            self.present(picker, animated: true) 
        })) 
 
        // Option to pick Audio 
        alert.addAction(UIAlertAction(title: "Pick Audio", style: .default, handler: { _ in 
            let audioPicker = UIDocumentPickerViewController(forOpeningContentTypes: [UTType.audio]) 
            audioPicker.allowsMultipleSelection = false 
            audioPicker.delegate = self 
            self.present(audioPicker, animated: true) 
        })) 
 
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) 
 
        self.present(alert, animated: true) 
    } 
     
    @IBAction func button_pauseVideo(_ sender: UIButton) { 
        sender.isEnabled = false 
        if sender.isSelected { 
            sender.setTitle("Pause", for: .normal) 
            uploadIsPaused = false 
            pauseBtnClicked = true 
            uploader.resume() 
            sender.backgroundColor = .systemGreen 

        } else { 
            sender.setTitle("Resume", for: .normal) 
            uploadIsPaused = true 
            pauseBtnClicked = false 
            startMonitoring() 
            uploader.pause() 
            sender.backgroundColor = .systemRed 
        } 

        sender.isSelected.toggle() 
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 
            sender.isEnabled = true 

        } 
    } 
     
    @IBAction func button_uploadVideo(_ sender: UIButton) { 
        button_upload.isHidden = true 
        button_pause.isHidden = false 
        button_abort.isHidden = false 
        self.progressBar.isHidden = false 
        self.label_progress.isHidden = false 
        self.label_progress.text = String(format: "%.0f%%", 0.0000000 * 100) 
         
        Task { 
            let createUploadURL = try await self.myServerBackend.createDirectUpload() 
            uploader.uploadFile(file: file!, endpoint: createUploadURL.absoluteString, chunkSizeKB: Int(chunkSize.text ?? "") ?? 0) 
        } 
    } 
     
    @IBAction func button_abort(_ sender: UIButton) { 
        button_pause.isHidden = true 
        button_abort.isHidden = true 
        button_upload.isHidden = false 
        uploader.abort() 
        sender.backgroundColor = .red 
    } 
     
    @IBAction func button_selectVideo(_ sender: UIButton) { 
        pickerConfig() 
    } 
     
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { 
        dismiss(animated: true, completion: nil) 
        guard let result = results.first else { 
            return 
        } 
         
        if let assetRequestId = self.assetRequestId { 
            PHImageManager.default().cancelImageRequest(assetRequestId) 
        } 
        let tempDir = FileManager.default.temporaryDirectory 
        let tempFile = URL(string: "upload-\(Date().timeIntervalSince1970).mp4", relativeTo: tempDir)! 
        
        guard let assetIdentitfier = result.assetIdentifier else { 
            print("No Asset ID for chosen asset") 
            return 
        } 

        guard let assetIdentitfier = result.assetIdentifier else { 
            print("No Asset ID for chosen asset") 
            return 

        } 

        let options = PHFetchOptions() 
        options.includeAssetSourceTypes = [.typeUserLibrary, .typeCloudShared] 
        let phAssetResult = PHAsset.fetchAssets(withLocalIdentifiers: [assetIdentitfier], options: options) 
        guard let phAsset = phAssetResult.firstObject else { 
            return 
        } 

        let exportOptions = PHVideoRequestOptions() 
        exportOptions.isNetworkAccessAllowed = true 
        exportOptions.deliveryMode = .highQualityFormat 
        assetRequestId = PHImageManager.default().requestExportSession(forVideo: phAsset, options: exportOptions, exportPreset: AVAssetExportPresetHighestQuality, resultHandler: {(exportSession, info) -> Void in 
            DispatchQueue.main.async { [weak self] in 
                guard let exportSession = exportSession else { 
                    return 
                } 
                self?.thumGenerater.extractThumbnailAsync(exportSession.asset) { thumbnailImage in 
                    guard let thumbImage = thumbnailImage else { return } 
                    DispatchQueue.main.async { 
                        self?.thumbNailImage.image = UIImage(cgImage: thumbImage) 
                        self?.thumbNailImage.isHidden = false 
                        self?.button_selectVideo.isUserInteractionEnabled = false 
                    } 
                } 
            } 
        }) 
         
        result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier , completionHandler: { [ weak self] url, error in 
            guard let fileURL = url else { 
                return 
            } 
            let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first 
            guard let targetURL = documentsDirectory?.appendingPathComponent(fileURL.lastPathComponent) else { return } 
            self?.file = (documentsDirectory?.appendingPathComponent(fileURL.lastPathComponent))! 
             
            do { 
                if FileManager.default.fileExists(atPath: targetURL.path) { 
                    try FileManager.default.removeItem(at: targetURL) 
                } else { 
                    //                    print("file path not exists") 

                } 
                 
                try FileManager.default.copyItem(at: fileURL, to: targetURL) 
                self?.videoInputURL = targetURL 
                DispatchQueue.main.async { 
                    self?.button_upload.isEnabled = true 
                    self?.button_upload.backgroundColor = .green 
                } 
            } catch { 
                self?.displayErrorAlert(error: error.localizedDescription) 
            } 
        }) 
    } 
     
    func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { 
        guard let fileURL = urls.first else { return } 
 
        // Start accessing security-scoped resource 
        let didStartAccessing = fileURL.startAccessingSecurityScopedResource() 
        defer { 
            if didStartAccessing { 
                fileURL.stopAccessingSecurityScopedResource() 
            } 
        } 
 
        // Confirm access granted 
        guard didStartAccessing else { 
            print("Failed to access security-scoped resource") 
            self.displayErrorAlert(error: "Unable to access selected file due to security permissions.") 
            return 
        } 
 
        // Now safe to use fileURL 
        print("Picked audio file: \(fileURL.lastPathComponent)") 
 
        // Copy to local directory 
        let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first 
        guard let targetURL = documentsDirectory?.appendingPathComponent(fileURL.lastPathComponent) else { return } 
        self.file = documentsDirectory?.appendingPathComponent(fileURL.lastPathComponent) 
  
        do { 
            if FileManager.default.fileExists(atPath: targetURL.path) { 
                try FileManager.default.removeItem(at: targetURL) 
            } 
            try FileManager.default.copyItem(at: fileURL, to: targetURL) 
 
            self.videoInputURL = targetURL  // Or self.audioInputURL 
            DispatchQueue.main.async { 
                self.button_upload.isEnabled = true 
                self.button_upload.backgroundColor = .green 
            } 
        } catch { 
            self.displayErrorAlert(error: error.localizedDescription) 
        } 
    } 
 
    func pickerDidCancel(_ picker: PHPickerViewController) { 
        dismiss(animated: true, completion: nil) 
    } 

    // Display an error message 
    func displayErrorAlert(error: String) { 
        let alertController = UIAlertController(title: "Error", message: error, preferredStyle: .alert) 
        let okAction = UIAlertAction(title: "OK", style: .default) { [weak self] _ in 
            DispatchQueue.main.async { 
                self?.thumbNailImage.isHidden = false 
                self?.button_selectVideo.isUserInteractionEnabled = true 
                if error == "The current upload was aborted. Please try uploading a new video" { 
                    self?.navigationController?.popViewController(animated: true) 
                } 
            } 
        } 
        alertController.addAction(okAction) 
        present(alertController, animated: true, completion: nil) 
    } 
     
    deinit { 
        monitor.cancel() 
        NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil) 
    } 
} 
 
class UploadURLGenerater  { 
     
    public  func presentErrorAlert(on viewController: UIViewController, withTitle title: String, message: String) { 
        let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) 
        let okAction = UIAlertAction(title: "OK", style: .default, handler: nil) 
        alertController.addAction(okAction) 
        viewController.present(alertController, animated: true, completion: nil) 
    } 
     
    func createDirectUpload() async throws -> URL { 
         
        let parameters: [String: Any] = [ 
            "corsOrigin": "*", 
            "pushMediaSettings": [ 
                "accessPolicy": "public", 
                "generateSubtitles": true, 
                "normalizeAudio": true, 
                "maxResolution": "1080p" 
            ] 
        ] 
         
        guard let jsonData = try? JSONSerialization.data(withJSONObject: parameters) else { 
            throw NSError(domain: "com.example.app", code: 500, userInfo: [NSLocalizedDescriptionKey: "Failed to serialize parameters"]) 
        } 
         
        let request = try { 
            var req = try URLRequest(url:fullURL(forEndpoint: "upload")) 
            req.httpBody = jsonData 
            req.httpMethod = "POST" 
            req.addValue("application/json", forHTTPHeaderField: "Content-Type") 
            req.addValue("application/json", forHTTPHeaderField: "accept") 
             
            let basicAuthCredential = "\(FASTPIX_ACCESS_TOKEN_ID):\(FASTPIX_SECRET_KEY)".data(using: .utf8)!.base64EncodedString() 
            req.addValue("Basic \(basicAuthCredential)", forHTTPHeaderField: "Authorization") 
            return req 
        }() 

         

        let (data, response) = try await urlSession.data(for: request) 
        let httpResponse = response as! HTTPURLResponse 
        print("Response",httpResponse.statusCode) 
        var uploadUrl: URL? 
        if (200...299).contains(httpResponse.statusCode) { 
            do { 
                // Assuming responseData is the Data object you want to convert to a dictionary 
                if let jsonDictionary = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { 
                    var dataDic = jsonDictionary["data"] as? NSDictionary 
                    uploadUrl = URL(string: dataDic?.value(forKey: "url") as? String ?? "") 
                } else { 
                    print("Failed to convert data to dictionary") 
                } 
            } catch { 
                print("Error converting data to dictionary: \(error.localizedDescription)") 
            } 
            guard let uploadUrl = uploadUrl else { return URL(string: "")!} 
            return uploadUrl 
        } else { 
            throw CreateUploadError(message: "Upload POST failed: HTTP \(httpResponse.statusCode):\n\(String(decoding: data, as: UTF8.self))") 
        } 
    } 
     
    /// Generates a full URL for a given endpoint in the FastPix Video public API 
    private func fullURL(forEndpoint: String) throws -> URL { 
        guard let url = URL(string: "https://venus-v1.fastpix.dev/on-demand/\(forEndpoint)") else { 
            throw CreateUploadError(message: "bad endpoint") 
        } 
        return url 
    } 
     
    //    https://dev-api.fastpix.io 
    let urlSession: URLSession 
    let jsonEncoder: JSONEncoder 
    let jsonDecoder: JSONDecoder 
     
    //Gowtham Venus 
    let FASTPIX_ACCESS_TOKEN_ID = "ACCESS TOKEN" 
    let FASTPIX_SECRET_KEY = "SECRET KEY" 
     
    init(urlSession: URLSession) { 
        self.urlSession = urlSession 
        self.jsonEncoder = JSONEncoder() 
        self.jsonEncoder.keyEncodingStrategy = JSONEncoder.KeyEncodingStrategy.convertToSnakeCase 
        self.jsonDecoder = JSONDecoder() 
        self.jsonDecoder.keyDecodingStrategy = JSONDecoder.KeyDecodingStrategy.convertFromSnakeCase 
    } 
     
    convenience init() { 
        self.init(urlSession: URLSession(configuration: URLSessionConfiguration.default)) 
    } 
} 
 
struct CreateUploadError : Error { 
    let message: String 
} 
 
fileprivate struct CreateUploadPost: Codable { 
    var newAssetSettings: NewAssetSettings = NewAssetSettings() 
    var corsOrigin: String = "*" 
} 
 
fileprivate struct NewAssetSettings: Codable { 
    var playbackPolicy: [String] = ["public"] 
    var passthrough: String = "Extra video data. This can be any data and it's for your use" 
    var mp4Support: String = "standard" 
    var normalizeAudio: Bool = true 
    var test: Bool = false 
} 
 
fileprivate struct CreateUploadResponse: Decodable { 
    var url: String 
    var id: String 
    var timeout: Int64 
    var status: String 
} 
 
fileprivate struct CreateUploadResponseContainer: Decodable { 
    var data: CreateUploadResponse 
} 

Handling network throttling

Network throttling is the intentional slowing down of internet speeds by an Internet Service Provider (ISP) to manage network congestion.

FastPixUploadSDK is designed to handle network throttling efficiently, ensuring smooth video uploads even when network conditions are less than ideal. It automatically detects and adapts to bandwidth throttling, optimizing video upload speeds within the allowed data limits to ensure that the upload process continues without disruption.


Error handling

Proper error handling ensures the SDK gracefully handles failures and provides meaningful feedback.


Common error scenarios:

  • Network failures
  • Timeout errors
  • Invalid data or server responses
  • Permission issues

Implementation example:

import fp_swift_upload_sdk 
 
class ViewController: UIViewController, UploadSDKErrorDelegate { 
    var uploader = Uploads() 
 
    override func viewDidLoad() { 
        super.viewDidLoad() 
        uploader.errorDelegate = self 
    } 
} 

This guide walks you through everything you need to integrate direct video uploads into your iOS app using the FastPix iOS Upload SDK: from SDK installation and signed URL generation to resumable uploads, progress tracking, and error handling.