Как сделать так, чтобы фотографии из фотоальбома пользователей загружались быстрее в коллекцию ViewView - PullRequest
0 голосов
/ 09 мая 2018

Я пытаюсь загрузить фотографии из локального фотоальбома пользователя в UICollectionView.Когда приложение запускается, загрузка collectionView занимает много времени.Я попытался проиндексировать проект и обнаружил, что самая медленная часть проистекает из того места, где вынимаются фотографии.Ниже выделен код из индекса

Код помечен из индекса

 let imageView = cell.imgView
        let requestOptions = PHImageRequestOptions()
        requestOptions.isSynchronous = true
        requestOptions.deliveryMode = .fastFormat
        let scale = min(2.0, UIScreen.main.scale)
        let requestImageSize = CGSize(width: cell.bounds.width * scale, height: cell.bounds.height * scale)
        PHImageManager.default().requestImage(for: fetchResult.object(at: indexPath.item) , targetSize: requestImageSize, contentMode: .aspectFill, options: requestOptions, resultHandler: {
            image,error in
            if let error = error{
                print(error)
            }
            self.imageArray.append(image!)
            imageView.image = image!
            imageView.clipsToBounds = false
            imageView.contentMode = .scaleAspectFill
        })

Весь код проекта

import UIKit
import Photos

class ViewController: UIViewController,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout,UICollectionViewDataSource,GreedoCollectionViewLayoutDataSource {

    var imageArray:[UIImage] = [UIImage]()
     var collectionViewSizeCalculator:GreedoCollectionViewLayout?

    var collectionView:UICollectionView! = {
        let layout = UICollectionViewFlowLayout()
        layout.minimumInteritemSpacing = 1
        layout.minimumLineSpacing = 2.0
        layout.scrollDirection = .vertical
        let CV = UICollectionView(frame: UIScreen.main.bounds, collectionViewLayout: layout)
        CV.translatesAutoresizingMaskIntoConstraints = false
        return CV
    }()


    var fetchResult: PHFetchResult<PHAsset> = PHFetchResult()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.collectionViewSizeCalculator = GreedoCollectionViewLayout(collectionView: self.collectionView)
        self.collectionViewSizeCalculator?.dataSource = self
        self.collectionViewSizeCalculator?.rowMaximumHeight = self.collectionView.bounds.height / 3;
        let layout = UICollectionViewFlowLayout()
        layout.minimumInteritemSpacing = 5.0
        layout.minimumLineSpacing = 5.0
        layout.sectionInset = UIEdgeInsetsMake(10.0, 5.0, 5.0, 5.0)
        self.collectionView.collectionViewLayout = layout
        //End Of new Greedo Stuff in ViewDidLoad()



        self.collectionView.delegate = self
        self.collectionView.dataSource = self
        collectionView.register(customPhotoCell.self, forCellWithReuseIdentifier: "Cell")
        self.collectionView.backgroundColor = .white
        self.view.addSubview(collectionView)


        setUpLayout()
        fetchAssets()

        let photos = PHPhotoLibrary.authorizationStatus()
        if photos == .notDetermined {
            PHPhotoLibrary.requestAuthorization({status in
                if status == .authorized{
                    print("permission granted")
                    DispatchQueue.main.async {
                        self.fetchAssets()
                        self.collectionView.reloadData()
                    }
                } else {
                    print("permission not granted")
                }
            })
        }


    }

    func setUpLayout(){
        collectionView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0).isActive = true
        collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0).isActive = true
        collectionView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0).isActive = true
        collectionView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true

    }

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



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

        let imageView = cell.imgView
        let requestOptions = PHImageRequestOptions()
        requestOptions.isSynchronous = true
        requestOptions.deliveryMode = .highQualityFormat
        let scale = min(2.0, UIScreen.main.scale)
        let requestImageSize = CGSize(width: cell.bounds.width * scale, height: cell.bounds.height * scale)
        PHImageManager.default().requestImage(for: fetchResult.object(at: indexPath.item) , targetSize: requestImageSize, contentMode: .aspectFill, options: requestOptions, resultHandler: {
            image,error in
            if let error = error{
                print(error)
            }
            self.imageArray.append(image!)
            imageView.image = image!
            imageView.clipsToBounds = false
            imageView.contentMode = .scaleAspectFill
        })
        cell.layer.masksToBounds = true
        cell.layer.cornerRadius = 10
        cell.backgroundColor = .white
        return cell
    }

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

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 5.0
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return 5.0
    }



    //Greedo Size for itemAt
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return self.collectionViewSizeCalculator!.sizeForPhoto(at: indexPath)
    }

    func greedoCollectionViewLayout(_ layout: GreedoCollectionViewLayout?, originalImageSizeAt indexPath: IndexPath?) -> CGSize {
        if (indexPath?.item)! < fetchResult.count{
            let asset = fetchResult[(indexPath?.item)!]
            return CGSize(width: asset.pixelWidth, height: asset.pixelHeight)
        }
        return CGSize(width: 0.1, height: 0.1)
    }

    //New stuff
    var lastOffsetY:CGFloat = 0
    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        lastOffsetY = scrollView.contentOffset.y
    }
    func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
        if(scrollView.contentOffset.y > self.lastOffsetY){
            UIView.animate(withDuration: 1.0, animations: {
                self.collectionView.transform = CGAffineTransform(translationX: 0, y: -50)
            }, completion: nil)
        }
    }
}



class customPhotoCell:UICollectionViewCell{

    var imgView:UIImageView = {
        let imgV = UIImageView()
        imgV.backgroundColor = .orange
        imgV.translatesAutoresizingMaskIntoConstraints = false
        return imgV
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        print("Inside of init")
        self.addSubview(imgView)
        self.layer.cornerRadius = 10.0
        imgView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
        imgView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
        imgView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0).isActive = true
        imgView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0).isActive = true
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }


}

extension UIImageView{
    func fetchImage(asset: PHAsset, contentMode: PHImageContentMode, targetSize: CGSize) {
        let options = PHImageRequestOptions()
        options.version = .original
        PHImageManager.default().requestImage(for: asset, targetSize: targetSize, contentMode: contentMode, options: options) { image, _ in
            guard let image = image else { return }
            switch contentMode {
            case .aspectFill:
                self.contentMode = .scaleAspectFill
            case .aspectFit:
                self.contentMode = .scaleAspectFit
            }
            self.image = image
        }
    }
}

extension UIImage{
    func resizeImageWith() -> UIImage {
        let aspectRatio = CGFloat(self.size.width / self.size.height)
        let newWidth = UIScreen.main.bounds.width
        let newSize = CGSize(width: newWidth, height: newWidth/aspectRatio)
        UIGraphicsBeginImageContextWithOptions(newSize, true, 0)
        self.draw(in: CGRect(origin: CGPoint(x: 0, y: 0), size: newSize))
        let  newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return newImage!
    }
}

Дополнительный код макета

protocol GreedoSizeCalculatorDataSource {
    func greedoSizeCalculator(_ layout: GreedoSizeCalculator?, originalImageSizeAt indexPath: IndexPath?) -> CGSize
}

class GreedoSizeCalculator {
     //private var _sizeCache = [AnyHashable: Any]()
    private var sizeCache = [IndexPath:CGSize]()
    private var leftOvers:[CGSize] = [CGSize]()
    var lastIndexPathAdded:IndexPath?


    var dataSource: GreedoSizeCalculatorDataSource?
    var rowMaximumHeight: CGFloat = 0.0
    var isFixedHeight = false
    var contentWidth: CGFloat = 0.0
    var interItemSpacing: CGFloat = 2.0


    func sizeForPhoto(at indexPath:IndexPath?)->CGSize{
        print("I made it rowHeight: \(rowMaximumHeight)")
        if let indexPath = indexPath{
            if sizeCache[indexPath] == nil{
                lastIndexPathAdded = indexPath
                computeSizes(at:indexPath)
            }
        }
        var size: CGSize? = sizeCache[indexPath!]
        if((size?.width)! < CGFloat(0.0) || (size?.height)! < CGFloat(0.0)){
            size = CGSize.zero
        }
        return size!
    }

    func clearCache(){
        self.sizeCache.removeAll()
    }

    func clearCache(after indexPath:IndexPath?){
        sizeCache.removeValue(forKey: indexPath!)

        for existingIndexPath in sizeCache.keys{
            if indexPath?.compare(existingIndexPath) == .orderedDescending{
                sizeCache.removeValue(forKey: existingIndexPath)
            }
        }

    }

    func computeSizes(at indexPath:IndexPath?){
        var photoSize = dataSource?.greedoSizeCalculator(self, originalImageSizeAt: indexPath)
        if (photoSize?.width)! < CGFloat(1) || (photoSize?.height)! < CGFloat(1) {
            // Photo with no height or width
            print("Photo has no height or width")
            photoSize?.width = rowMaximumHeight
            photoSize?.height = rowMaximumHeight
        }
        print("The photo size at index: \((indexPath?.item)!) is \nwidth: \((photoSize?.width)!), height: \((photoSize?.height)!)")
        leftOvers.append(photoSize!)
        print("Index:\(indexPath?.item) leftOvers.count: \(leftOvers.count)")
        var enoughContentForTheRow = false
        var rowHeight:CGFloat = rowMaximumHeight
        var widthMultiplier:CGFloat = 1.0
        //Calculations For Variable HeightGrid
        var totalAspectRatio:CGFloat = 0.0
        for leftOver in leftOvers{
            print("current aspect ratio: \(leftOver.width/leftOver.height)")
            totalAspectRatio += leftOver.width/leftOver.height
        }
        print("totalAspectRatio: \(totalAspectRatio)")
        //Aspect Ratio Calculation how to find the height of the row!!!!!!!!!!!!!!!!!!!!!!!
        rowHeight = CGFloat(contentWidth)/CGFloat(totalAspectRatio)
        print("rowHeight: \(rowHeight)")
        enoughContentForTheRow = rowHeight < rowMaximumHeight

        if enoughContentForTheRow {
            var availableSpace: CGFloat = contentWidth
            print("Available space: \(availableSpace)")
            var index: Int = 0
            for leftOver in leftOvers{
                var newWidth:CGFloat = floor((rowHeight*leftOver.width)/leftOver.height)
                print("new width: \(newWidth)")
                newWidth = min(availableSpace, newWidth)

            //add the size into the cahce
                self.sizeCache[lastIndexPathAdded!] = CGSize(width: newWidth, height: rowHeight)
                availableSpace -= newWidth
                availableSpace -= self.interItemSpacing
                // We need to keep track of the last index path added
                self.lastIndexPathAdded = IndexPath(item:(lastIndexPathAdded?.item)!+1,section:(lastIndexPathAdded?.section)!)
                index += 1
            }
            leftOvers.removeAll()
        }
        else{
            // The line is not full, let's ask the next photo and try to fill up the line
            computeSizes(at: IndexPath(item:(indexPath?.item)!+1,section:(indexPath?.section)!))
        }
    }


}

protocol GreedoCollectionViewLayoutDataSource {
    func greedoCollectionViewLayout(_ layout: GreedoCollectionViewLayout?, originalImageSizeAt indexPath: IndexPath?) -> CGSize
}

class GreedoCollectionViewLayout:GreedoSizeCalculatorDataSource {
    var dataSource: GreedoCollectionViewLayoutDataSource?
    var rowMaximumHeight: CGFloat = 0.0
    var isFixedHeight = false

    var collectionView:UICollectionView?
    private var _greedo: GreedoSizeCalculator?
    var greedo: GreedoSizeCalculator? {
        if _greedo == nil {
            _greedo = GreedoSizeCalculator()
            _greedo?.rowMaximumHeight = 200
            _greedo?.isFixedHeight = false
            _greedo?.dataSource = self
        }
        return _greedo
    }

    init(collectionView: UICollectionView?) {
        self.collectionView = collectionView
    }

    func sizeForPhoto(at indexPath: IndexPath?) -> CGSize {
        var contentWidth:CGFloat = (self.collectionView?.bounds.size.width)!
        var interitemspacing:CGFloat = 2.0
        let layout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout

        if layout != nil {
            contentWidth -= (layout?.sectionInset.left ?? 0.0) + (layout?.sectionInset.right ?? 0.0)
            interitemspacing = (layout?.minimumInteritemSpacing)!
            layout?.minimumLineSpacing = 5.0
            layout?.minimumInteritemSpacing = 5.0
        }
        greedo?.contentWidth = contentWidth
        greedo?.interItemSpacing = interitemspacing
        return (greedo?.sizeForPhoto(at: indexPath))!
    }

    func clearCache() {
        greedo?.clearCache()
    }

    func clearCache(after indexPath: IndexPath?) {
        greedo?.clearCache(after: indexPath)
    }

    func rowmaximumHeight() -> CGFloat {
        return (greedo?.rowMaximumHeight)!
    }

    func setRowMaximumHeight(_ rowMaximumHeight: CGFloat) {
        greedo?.rowMaximumHeight = rowMaximumHeight
    }

    func greedoSizeCalculator(_ layout: GreedoSizeCalculator?, originalImageSizeAt indexPath: IndexPath?) -> CGSize {
        return dataSource!.greedoCollectionViewLayout(self, originalImageSizeAt: indexPath)
    }


}
...