Get file size of PHAsset without loading in the resource?
Asked Answered
O

4

15

Is there a way to get the file size on disk of a PHAsset without doing requestImageDataForAsset or converting it to ALAsset? I am loading in previews of a user's photo library - potentially many thousands of them - and it's imperative to my app that they know the size before import.

Ovarian answered 14/7, 2017 at 19:58 Comment(0)
O
33

You can grab the fileSize of a PHAsset and convert it to human readable form like this:

let resources = PHAssetResource.assetResources(for: yourAsset) // your PHAsset
          
var sizeOnDisk: Int64? = 0
        
if let resource = resources.first {
    let unsignedInt64 = resource.value(forKey: "fileSize") as? CLong
    sizeOnDisk = Int64(bitPattern: UInt64(unsignedInt64!))
}

Then use your sizeOnDisk variable and pass it into a method like this...

func converByteToHumanReadable(_ bytes:Int64) -> String {
     let formatter:ByteCountFormatter = ByteCountFormatter()
     formatter.countStyle = .binary
     
     return formatter.string(fromByteCount: Int64(bytes))
 }
Ovarian answered 14/7, 2017 at 19:58 Comment(5)
It's not in any documentation because it's accessing the header files. Technically this is a small piece of Apple's private APIs. However, we have submitted a build and received full approval without issue.Ovarian
Is it safety to take only first item? As apple said: " An asset can contain multiple resources - for example, an edited photo asset contains resources for both the original and edited images".Conformance
We'll probably need to check the resources for the image resources: kUTTypeJPEG, AVFileType.heic.rawValue, kUTTypePNG, kUTTypeTIFF, kUTTypeRawImagePalomo
UPD. But I noticed that the resources.last is always the most recent resource (iOS 13).Palomo
For iCloud albums video and images, I'm getting zero (0) as size.Vandalize
I
4

SWIFT 5.0 Light & Easy:

private static let bcf = ByteCountFormatter()

func getSize(asset: PHAsset) -> String {
    let resources = PHAssetResource.assetResources(for: asset)

    guard let resource = resources.first,
          let unsignedInt64 = resource.value(forKey: "fileSize") as? CLong else {
              return "Unknown"
          }

    let sizeOnDisk = Int64(bitPattern: UInt64(unsignedInt64))
    Self.bcf.allowedUnits = [.useMB]
    Self.bcf.countStyle = .file
    return Self.bcf.string(fromByteCount: sizeOnDisk)
}
Iva answered 1/10, 2021 at 1:7 Comment(0)
S
2

A safer solution:

[asset requestContentEditingInputWithOptions:nil completionHandler:^(PHContentEditingInput * _Nullable contentEditingInput, NSDictionary * _Nonnull info) {
    NSNumber *fileSize = nil;
    NSError *error = nil;
    [contentEditingInput.fullSizeImageURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:&error];
    NSLog(@"file size: %@\nerror: %@", fileSize, error);
}];

Swift version:

asset.requestContentEditingInput(with: nil) { (contentEditingInput, _) in
    do {
        let fileSize = try contentEditingInput?.fullSizeImageURL?.resourceValues(forKeys: [URLResourceKey.fileSizeKey]).fileSize
        print("file size: \(String(describing: fileSize))")
    } catch let error {
        fatalError("error: \(error)")
    }
}

Inspired by How to get an ALAsset URL from a PHAsset?

Skinner answered 12/10, 2018 at 1:32 Comment(2)
This method is so heavy. Won't work for the thousands of assets.Palomo
Yes, so it is better to execute in a background thread and cache the result.Skinner
I
2

Please try this.

let resources = PHAssetResource.assetResources(for: YourAsset)
var sizeOnDisk: Int64 = 0

if let resource = resources.first {
   let unsignedInt64 = resource.value(forKey: "fileSize") as? CLong
   sizeOnDisk = Int64(bitPattern: UInt64(unsignedInt64!))

   totalSize.text = String(format: "%.2f", Double(sizeOnDisk) / (1024.0*1024.0))+" MB"
}
Ironsides answered 29/8, 2020 at 1:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.