1

I currently have a custom UICollection which loads a users video library from their camera roll. Now I am currently able to add all the videos into an array; and it prints out the correct count of videos; however my UICollection is not displaying all of my videos in my library (which amounts to 119). Anyone have any clue why this would be occurring?

Here is my code:

struct Media {
        var image:UIImage?
        var videoURL:NSURL?
    }

    var mediaArray = [Media]()


    func grabPhotos(){
        let imgManager = PHImageManager.default()

        let requestOptions = PHImageRequestOptions()
        requestOptions.isSynchronous = true
        requestOptions.deliveryMode = .highQualityFormat

        let fetchOptions = PHFetchOptions()
        fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
        if let fetchResult : PHFetchResult = PHAsset.fetchAssets(with: .video, options: fetchOptions) {


            if fetchResult.count > 0 {
                for i in 0..<fetchResult.count{
                    var mediaItem = Media()
                    //Used for fetch Image//
                    imgManager.requestImage(for: fetchResult.object(at: i) as PHAsset , targetSize: CGSize(width: 400, height: 400), contentMode: .aspectFit, options: requestOptions, resultHandler: {
                        image, error in
                        let imageOfVideo = image! as UIImage
                        mediaItem.image = imageOfVideo;
                        //Used for fetch Video//
                        imgManager.requestAVAsset(forVideo: fetchResult.object(at: i) as PHAsset, options: PHVideoRequestOptions(), resultHandler: {(avAsset, audioMix, info) -> Void in
                            if let asset = avAsset as? AVURLAsset {
                                let videoData = NSURL(string: "\(asset.url)")
                                let duration : CMTime = asset.duration
                                let durationInSecond = CMTimeGetSeconds(duration)
                                print(durationInSecond)
                                mediaItem.videoURL = videoData!
                                self.mediaArray.append(mediaItem)
                                print(self.mediaArray.count)

                            }

                        })
                    })
                }

            }
            else{
                //showAllertToImportImage()//A function to show alert
            }
        }
    }

And my cellForItemAt

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
    {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! VideoSelectionCVCell

        cell.uploadedFile.image = mediaArray[indexPath.row].image 


        return cell
    }

& Within my viewWillAppear I have the following creating the UICollection:

let flowLayout = UICollectionViewFlowLayout()


        let collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: flowLayout)
        collectionView.register(VideoSelectionCVCell.self, forCellWithReuseIdentifier: cellId)

        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.backgroundColor = UIColor.clear

        self.view.addSubview(collectionView)

I think what is occurring is the screen is loading before the grabPhotos() occurs; and the grabPhotos() doesn't finish until the after the screen is loaded. I also have my UICollection being created in the viewWillAppear, so that would make it a private occurrence (if I'm correct). So I guess to fix this, I would need to make the UICollectionView public, but how would I do that if I am doing it programmatically + creating it in my View Will Appear?

AndrewS
  • 351
  • 4
  • 18
  • I'm not sure what causing it, have you tried `self.collectionView.reloadData()`? – Ennabah May 03 '17 at 02:22
  • I've tried reloading the whole screen in my for loop & in my viewWillAppear, but no luck. I also have the UICollection being set up in my viewWillAppear – AndrewS May 03 '17 at 02:31
  • What about `numberOfItemsInSection`? – Ennabah May 03 '17 at 02:34
  • I have it equal to the mediaArray.count (which is 119) – AndrewS May 03 '17 at 02:35
  • I think what is occurring is the screen is loading before the grabPhotos() occurs; and the grabPhotos() doesn't finish until the after the screen is loaded. I also have my UICollection being created in the viewWillAppear, so that would make it a private occurrence (if I'm correct). So I guess to fix this, I would need to make the UICollectionView public, but how would I do that if I am doing it programmatically + creating it in my View Will Appear? – AndrewS May 03 '17 at 02:44
  • 1
    To make sure you're right, I would create a button and manually `grabPhotos()`, just for testing. If that worked, then you need to figure out another way to `grabPhotos()` – Ennabah May 03 '17 at 02:46

1 Answers1

0

There are a couple different ways you can solve this I think.

Move your remote image loading into cellforitemat

  1. Add your collection view in Viewdidload, then at the end of the function call grabphotos.
  2. In your grabphotos functions, call collectionView.reloadData() after you have the fetchResult.
  3. Move imgManager.requestImage into cellforitem. This way you are only loading each image as the cells are rendered. The user isn't waiting for all the images to load before the collectionView is updated. You can add a prefetch if you are concerned about performance.

Use a DispatchGroup

If you really really want to load all the images before updating the collectionView, you can create a DispatchGroup to track the image downloads and then update the collectionView after it's all done.

struct Media {
    var image:UIImage?
    var videoURL:NSURL?
}

var mediaArray = [Media]()
let loadContentGroup = DispatchGroup()

func grabPhotos(){
    loadContentGroup.notify(queue: DispatchQueue.main) { [weak self] in                     
        guard let `self` = self else { return }
        self.collectionView.reloadData()
    }

    let imgManager = PHImageManager.default()

    let requestOptions = PHImageRequestOptions()
    requestOptions.isSynchronous = true
    requestOptions.deliveryMode = .highQualityFormat

    let fetchOptions = PHFetchOptions()
    fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
    if let fetchResult : PHFetchResult = PHAsset.fetchAssets(with: .video, options: fetchOptions) {


        if fetchResult.count > 0 {
            for i in 0..<fetchResult.count{
                var mediaItem = Media()

                loadContentGroup.enter()

                //Used for fetch Image//
                imgManager.requestImage(for: fetchResult.object(at: i) as PHAsset , targetSize: CGSize(width: 400, height: 400), contentMode: .aspectFit, options: requestOptions, resultHandler: {
                    image, error in
                    let imageOfVideo = image! as UIImage
                    mediaItem.image = imageOfVideo;
                    //Used for fetch Video//
                    imgManager.requestAVAsset(forVideo: fetchResult.object(at: i) as PHAsset, options: PHVideoRequestOptions(), resultHandler: {(avAsset, audioMix, info) -> Void in
                        if let asset = avAsset as? AVURLAsset {
                            let videoData = NSURL(string: "\(asset.url)")
                            let duration : CMTime = asset.duration
                            let durationInSecond = CMTimeGetSeconds(duration)
                            print(durationInSecond)
                            mediaItem.videoURL = videoData!
                            self.mediaArray.append(mediaItem)
                            print(self.mediaArray.count)

                            self.loadContentGroup.leave()
                        }

                    })
                })
            }

        }
        else{
            //showAllertToImportImage()//A function to show alert
        }
    }
}
George
  • 658
  • 5
  • 12
  • I like the idea of option 3; but I am a little confused on the implementation of it; would I just put the whole grabPhotos code in the cellForItem? and then after that function loads, I place my "cell.uploadedFile.image = mediaArray[indexPath.row].image " at the end? – AndrewS May 03 '17 at 14:41
  • Yep. Get your media item from fetchResult using the indexPath. Then switch back to main thread once you have the photo for the specific cell to set the cell.uploadedFileimage to the image. You don't need to load images for the entire array. Just the one for the cell. – George May 04 '17 at 05:18