BS
BleepingSwift
Published on

> Getting Current Disk Usage in Swift

Authors
  • avatar
    Name
    Mick MacCallum
    Twitter
    @0x7fs

Knowing how much disk space is available can help your app make smart decisions—whether to download that large file, warn users before a sync, or clean up cached data proactively. iOS provides this information through URL resource values, which give you access to volume capacity data for any file system location.

Reading Volume Capacity

The URL type provides a resourceValues(forKeys:) method that returns information about the file system. For disk usage, the relevant keys are in the URLResourceKey enum:

import Foundation

func getDiskUsage() -> (total: Int64, available: Int64)? {
    let fileManager = FileManager.default
    guard let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
        return nil
    }

    do {
        let values = try documentsURL.resourceValues(forKeys: [
            .volumeTotalCapacityKey,
            .volumeAvailableCapacityKey
        ])

        guard let total = values.volumeTotalCapacity,
              let available = values.volumeAvailableCapacity else {
            return nil
        }

        return (Int64(total), Int64(available))
    } catch {
        return nil
    }
}

The volumeTotalCapacityKey returns the total size of the volume in bytes, while volumeAvailableCapacityKey returns the free space. These values come from the same volume where your documents directory lives—which on iOS is the device's main storage.

Important vs Opportunistic Capacity

iOS distinguishes between space available for "important" operations and space for "opportunistic" ones. The system reserves some buffer space that it won't report as available for opportunistic use, but will allow for critical operations:

func getDetailedDiskUsage() -> DiskUsage? {
    let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!

    do {
        let values = try documentsURL.resourceValues(forKeys: [
            .volumeTotalCapacityKey,
            .volumeAvailableCapacityForImportantUsageKey,
            .volumeAvailableCapacityForOpportunisticUsageKey
        ])

        return DiskUsage(
            total: values.volumeTotalCapacity.map(Int64.init) ?? 0,
            availableForImportant: values.volumeAvailableCapacityForImportantUsage ?? 0,
            availableForOpportunistic: values.volumeAvailableCapacityForOpportunisticUsage ?? 0
        )
    } catch {
        return nil
    }
}

struct DiskUsage {
    let total: Int64
    let availableForImportant: Int64
    let availableForOpportunistic: Int64

    var usedSpace: Int64 {
        total - availableForImportant
    }

    var percentUsed: Double {
        guard total > 0 else { return 0 }
        return Double(usedSpace) / Double(total) * 100
    }
}

Use volumeAvailableCapacityForImportantUsageKey when checking if there's room for user-initiated downloads or saves—things the user explicitly requested. Use volumeAvailableCapacityForOpportunisticUsageKey for background caching or prefetching where you want to be more conservative about space usage.

Formatting for Display

When showing disk space to users, format the bytes into human-readable units:

extension Int64 {
    var formattedBytes: String {
        let formatter = ByteCountFormatter()
        formatter.countStyle = .file
        return formatter.string(fromByteCount: self)
    }
}

// Usage
if let usage = getDetailedDiskUsage() {
    print("Total: \(usage.total.formattedBytes)")
    print("Available: \(usage.availableForImportant.formattedBytes)")
    print("Used: \(String(format: "%.1f%%", usage.percentUsed))")
}

ByteCountFormatter automatically picks appropriate units (KB, MB, GB) and localizes the output for the user's locale.

Practical Applications

Check available space before starting large downloads:

func canDownloadFile(ofSize bytes: Int64) -> Bool {
    guard let usage = getDetailedDiskUsage() else { return false }
    // Leave some buffer space
    let requiredSpace = bytes + (50 * 1024 * 1024) // 50MB buffer
    return usage.availableForImportant >= requiredSpace
}

Monitor disk usage and clean caches when space is low:

func cleanupIfNeeded() {
    guard let usage = getDetailedDiskUsage() else { return }

    let fiveGB: Int64 = 5 * 1024 * 1024 * 1024
    if usage.availableForOpportunistic < fiveGB {
        clearImageCache()
        clearTemporaryFiles()
    }
}

The resource values API is synchronous and fast enough for occasional checks, but avoid calling it in tight loops or on the main thread during performance-critical operations.

subscribe.sh

// Stay Updated

Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.

>

By subscribing, you agree to our Privacy Policy.