Accessing videos from device's photo library in SwiftUI

50 Views Asked by At

I'm building an app for cleaning users' photo libraries, but I've hit a road-block when it comes to accessing videos. Here's the code for accessing and editing photos:

import Foundation
import Photos

class PhotosModel: ObservableObject {
    @Published var errorString : String = ""
    @Published var photoArray: [BetterUIImage] = []
    
    private var currentIndex = 0
    private var totalInitialPhotos = 0
    
    private var photosToDelete = [PHAsset]()
    
    init() {
        PHPhotoLibrary.requestAuthorization { (status) in
            DispatchQueue.main.async {
                switch status {
                case .authorized, .limited:
                    self.errorString = ""
                    self.getInitialPhotos()
                case .denied, .restricted:
                    self.errorString = "Photo access permission denied"
                case .notDetermined:
                    self.errorString = "Photo access permission not determined"
                @unknown default:
                    fatalError()
                }
            }
        }
    }
    
    fileprivate func getInitialPhotos() {
        let manager = PHImageManager.default()
        let requestOptions = PHImageRequestOptions()
        requestOptions.isSynchronous = false
        requestOptions.deliveryMode = .highQualityFormat
        let fetchOptions = PHFetchOptions()
        fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
        
        let results: PHFetchResult = PHAsset.fetchAssets(with: fetchOptions)
        totalInitialPhotos = results.count
        
        if results.count > 2 {
            for i in 0..<3 {
                let asset = results.object(at: i)
                let size = CGSize(width: 700, height: 700) //You can change size here
                manager.requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: requestOptions) { (image, _) in
                    DispatchQueue.main.async {
                        if let image = image {
                            let betterImage = BetterUIImage(image: image, asset: asset)
                            self.photoArray.append(betterImage)
                        } else {
                            print("error asset to image")
                        }
                    }
                }
            }
            currentIndex = 3
        } else if results.count > 0 {
            for i in 0..<results.count {
                let asset = results.object(at: i)
                let size = CGSize(width: 700, height: 700) //You can change size here
                manager.requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: requestOptions) { (image, _) in
                    DispatchQueue.main.async {
                        if let image = image {
                            let betterImage = BetterUIImage(image: image, asset: asset)
                            self.photoArray.append(betterImage)
                        } else {
                            print("error asset to image")
                        }
                    }
                }
            }
        } else {
            DispatchQueue.main.async {
                self.errorString = "No photos to display"
            }
        }
        self.photoArray = self.photoArray.reversed()
        print("getAllPhotos() completed")
    }
    
    func appendToPhotosToDelete(_ asset: PHAsset) {
        photosToDelete.append(asset)
        print("appendToPhotosToDelete() completed")
    }
    
    fileprivate func getNextPhoto() {
        let manager = PHImageManager.default()
        let requestOptions = PHImageRequestOptions()
        requestOptions.isSynchronous = false
        requestOptions.deliveryMode = .highQualityFormat
        let fetchOptions = PHFetchOptions()
        fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
        
        // TODO: Set change with .image to with fetchOptions: Gets all types
        let results: PHFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
        
        // TODO: Change photoArray so that it imcludes all three types
        self.photoArray = photoArray.shiftRight()
        
        let asset = results.object(at: currentIndex)
        let size = CGSize(width: 700, height: 700) //You can change size here
        
        // TODO: Make this so that it applies to Images, Lives, and Videos
        manager.requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: requestOptions) { (image, _) in
            DispatchQueue.main.async {
                if let image = image {
                    self.photoArray.removeLast()
                    self.photoArray.append(BetterUIImage(image: image, asset: asset))
                } else {
                    print("error asset to image")
                }
            }
        }
    }
    
    func next() {
        if currentIndex == totalInitialPhotos {
            self.photoArray = []
            return
        } else if currentIndex == totalInitialPhotos - 1 {
            self.photoArray.removeLast()
        } else if currentIndex == totalInitialPhotos - 2 {
            self.photoArray.removeLast()
        }
        
        getNextPhoto()
        
        currentIndex += 1
        print(currentIndex)
    }
    
    func deletePhoto() {
        PHPhotoLibrary.shared().performChanges({
            PHAssetChangeRequest.deleteAssets(NSArray(array: self.photosToDelete))
        })
    }
}

Is there a way to edit this system in order to return both images and videos? I tried by changing the PHAsset.fetchAssets(with: .image, options: fetchOptions) call to HAsset.fetchAssets(with: fetchOptions). That didn't seem to work, but also didn't break anything.

Is there another way to do this? I've seen other apps do something similar but all of the information I can find online involves PhotosPicker, which pretty much defeats the purpose of my app.

Edit: Remove unnecessary UIKit import.

1

There are 1 best solutions below

2
flanker On

I think you were on the right lines PHAsset.fetchAssets(with: fetchOptions) as this will allow the return of multiple media types. You might even be able to use it without any predicate at all, but if you want to specifically return just videos and images this works on my quick test:

import Photos

let format = "(mediaType = %d)  || (mediaType = %d)"
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: format,
                                     argumentArray: [PHAssetMediaType.image.rawValue, PHAssetMediaType.video.rawValue])

let fetchResult = PHAsset.fetchAssets(with: fetchOptions)

fetchResult.count is different depending if I just use .video, .image or both in the query, with the numbers feeling appropriate for my photo library.