2

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.

3
  • You want solution in SwiftUI or UIKit is not clear. I see that you have imported UIKit also used ObservedObject is making me confused. Commented Mar 28 at 16:08
  • @MahiAlJawad SwfitUI. I had imported UIKit at some point while getting things to work with photos. The documentation for the type of app I'm building is sparse so I used a number of methods before landing on this one. Sorry, just forgot to delete the import.
    – garrett
    Commented Mar 28 at 16:17
  • I would have thought using PHAsset.fetchAssets(with: fetchOptions and add a predicate to the fetchOptions that matches mediaType = .image || mediaType = .video would be the way to go. Will have a play when I'm on the Mac.
    – flanker
    Commented Mar 28 at 19:01

1 Answer 1

1

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.

2
  • Right now, I've got that and I think the problem might be the image request. Might have to just use requestImage if its an image and requestPlayer if its a video. I just need to figure out if the request has a function that returns the type of asset.
    – garrett
    Commented Mar 31 at 23:20
  • It does. fetchedAssets is a collection of PHAsset, which you can access like you would an NSArray, and each asset has a .mediaType property that you can check to determine what type of media the asset is.
    – flanker
    Commented Apr 1 at 1:30

Not the answer you're looking for? Browse other questions tagged or ask your own question.