12/08/2018, 14:59

Create a simple image picker just like the Camera roll

I will describe the way to make a image picker just like a simple camera roll Preparing a CollectionView, i will get the image from PHAsset that received from PHFetchResult then display it Moreover, i will be using prefetch of CollectionView that imported according to iOS10 Class definition ...

I will describe the way to make a image picker just like a simple camera roll Preparing a CollectionView, i will get the image from PHAsset that received from PHFetchResult then display it Moreover, i will be using prefetch of CollectionView that imported according to iOS10

Class definition

I define the variable and various constants. By changing the value of kColumnCnt and kCellSpacing, we can change the appearance of CollectionView The targetSize is the size of the CollectionViewCell and it is also the size of the image size to be read later. (We will temporarily assign CGSize.zero to calculate in initView.)

class ImagePickerViewController: UIViewController {
    @IBOutlet fileprivate weak var collectionView: UICollectionView!

    fileprivate let kCellReuseIdentifier = "Cell"
    fileprivate let kColumnCnt: Int = 3
    fileprivate let kCellSpacing: CGFloat = 2
    fileprivate var fetchResult: PHFetchResult<PHAsset>!
    fileprivate var imageManager = PHCachingImageManager()
    fileprivate var targetSize = CGSize.zero

    override func viewDidLoad() {
        super.viewDidLoad()

        initView()
        loadPhotos()
    }
}

Initiate View and Load the image

We need to decide the targetSize from the number of columns, the margin size, and the awidth of CollectionView. Moreover, in loadPhotos, PHFetchOptions reads photos in descending order of creation date and time. Lastly, Linking to the delegate of CollectionView and dataSource is executed on Interface Builder.

fileprivate extension ImagePickerViewController {
    fileprivate func initView() {
        let imgWidth = (collectionView.frame.awidth - (kCellSpacing * (CGFloat(kColumnCnt) - 1))) / CGFloat(kColumnCnt)
        targetSize = CGSize(awidth: imgWidth, height: imgWidth)

        let layout = UICollectionViewFlowLayout()
        layout.itemSize = targetSize
        layout.minimumInteritemSpacing = kCellSpacing
        layout.minimumLineSpacing = kCellSpacing
        collectionView.collectionViewLayout = layout

        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: kCellReuseIdentifier)
    }

    fileprivate func loadPhotos() {
        let options = PHFetchOptions()
        options.sortDescriptors = [
            NSSortDescriptor(key: "creationDate", ascending: false)
        ]
        fetchResult = PHAsset.fetchAssets(with: .image, options: options)
    }
}

Implementing UICollectionViewDataSource

i will implement the UICollectionViewDataSource. Get the PhotoAsset object with fetchResult.object (at: indexPath.item), then get the UIImage by requestImage.

extension ImagePickerViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kCellReuseIdentifier, for: indexPath)
        let photoAsset = fetchResult.object(at: indexPath.item)
        imageManager.requestImage(for: photoAsset, targetSize: targetSize, contentMode: .aspectFill, options: nil) { (image, info) -> Void in
            let imageView = UIImageView(image: image)
            imageView.frame.size = cell.frame.size
            imageView.contentMode = .scaleAspectFill
            imageView.clipsToBounds = true
            cell.contentView.addSubview(imageView)
        }
        return cell
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return fetchResult.count
    }
}

Implementing the UICollectionViewDelegate

Implements UICollectionViewDelegate to determine the size and section number of Cell. doSelectItemAt also describes the process when selecting a photo.

extension ImagePickerViewController: UICollectionViewDelegate {
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }

    func collectionView(_ collectionView: UICollectionView, layout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
        return targetSize
    }

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let photoAsset = fetchResult.object(at: indexPath.item)
        print(photoAsset.description)
    }
}

Implementing UICollectionViewDataSourcePrefetching

Implement UICollectionViewDataSourcePrefetching added from iOS 10. Cache the photo with prefetchItemsAt and release the cache with cancelPrefetchingForItemsAt.

extension ImagePickerViewController: UICollectionViewDataSourcePrefetching {
    func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
        DispatchQueue.main.async {
            self.imageManager.startCachingImages(for: indexPaths.map{ self.fetchResult.object(at: $0.item) }, targetSize: self.targetSize, contentMode: .aspectFill, options: nil)
        }
    }

    func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
        DispatchQueue.main.async {
            self.imageManager.stopCachingImages(for: indexPaths.map{ self.fetchResult.object(at: $0.item) }, targetSize: self.targetSize, contentMode: .aspectFill, options: nil)
        }
    }
}

There is a project on github Image-Picker@github (https://github.com/ayakix/Image-Picker)

0