iosswiftswiftuiphotospicker

SwiftUI PhotosPicker - get the size of picked item in Kilobytes


var body: some View {
    
    PhotosPicker(selection: $selectedItem, matching: .any(of: [.images, .videos]), photoLibrary: .shared()){
        Image(systemName: "camera.fill")
            .resizable()
            .aspectRatio(contentMode: .fit)
            .frame(height: 20)
            .padding(2)

    }
    .onChange(of: selectedItem) { newItem in
        
        // item size in kilobytes?
    }
}

Is there a way to get the size of selectedItem in Kilobytes?

I know how to do it by finding out the URL of the item first and then the size of it by the URL, but this is kinda a dumb workaround since the item is already selected and it's also kinda slow.


Solution

  • You can load the item and use the byte count.

    import SwiftUI
    import PhotosUI
    struct PhotoFileSize: View {
        @State private var selectedItem: PhotosPickerItem?
        
        var body: some View {
            PhotosPicker(selection: $selectedItem) {
                Text("Selected Item")
            }
            .task(id: selectedItem) {
                //Only run this code if there is a selected item
                guard let selectedItem else {return}
                
                do {
                    let result = try await selectedItem.loadTransferable(type: Data.self)
                    
                    switch result {
                    case .none:
                        break
                    case .some(let data):
                        let size = Measurement(value: Double(data.count), unit: UnitInformationStorage.bytes)
                        print(size.converted(to: .kilobytes).formatted())
                    }
                } catch {
                    print(error)
                }
            }
            
        }
    }
    
    #Preview {
        PhotoFileSize()
    }
    
    ///PhotosPhickerItem is a struct
    extension PhotosPickerItem: @unchecked Sendable {}
    

    or get the file size without loading the item into memory but it requires additional permissions.

    import SwiftUI
    import PhotosUI
    struct PhotoFileSize: View {
        @State private var selectedItem: PhotosPickerItem?
        //Required or `itemIdentifier` will be nil
        let library: PHPhotoLibrary = .shared()
        var body: some View {
            PhotosPicker(selection: $selectedItem, photoLibrary: library) {
                Text("Selected Item")
            }
            
            .task(id: selectedItem) {
                //Only run this code if there is a selected item
                guard let selectedItem else {return}
                //Get the local identifier
                guard let id = selectedItem.itemIdentifier else {return}
                
                //Must have authorization to access the PhotoLibrary/PHAsset directly
                guard await PHPhotoLibrary.requestAuthorization(for: .readWrite) == .authorized else {return}
    
                let asset = PHAsset.fetchAssets(withLocalIdentifiers: [id], options: nil).firstObject!
                
                let resources = PHAssetResource.assetResources(for: asset)
                //Get the file sizw
                guard let fileSize = resources.first?.fileSize else  {
                    return
                }
                    print(fileSize.converted(to: .kilobytes).formatted())
     
            }
        }
    }
    
    #Preview {
        PhotoFileSize()
    }
    
    extension PHAssetResource {
        var fileSize: Measurement<UnitInformationStorage>? {
                    
            guard let unsignedInt64 = self.value(forKey: "fileSize") as? CLong else {return nil}
            let sizeOnDisk = Int64(bitPattern: UInt64(unsignedInt64))
            return .init(value: Double(sizeOnDisk), unit: .bytes)
        }
    }