How to smoothly animate transition cells in uicollectionview in custom flowLayout

609 Views Asked by At

I want to create an app that shows an array of photos with captions (for now I have empty circles) that are arranged in horizontal uicollectionview. I want to make one larger circle to be displayed in the center of the screen, but the remaining circles in the line to be smaller and shaded. I did it by creating my own layout for uicollectionview. But I have one problem that I don't know how to fix it. I want the transition between circles to be smoothly animated. Here is an example of what I've done:

I want to achieve something like this:gif

I know that there are some library and frameworks which do that, but I'd like to do by my own. Here is my code:

class FlowViewController: UIViewController {

    let cellWidth : CGFloat = 275
    let cellHeight : CGFloat = 300
    let cellSpacing : CGFloat = 10
    
    private let circleCollectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout.init())
    private let layout = myCarouselFlowLayout()
    private let collectionViewCellIdentifier = "PositionCollectionViewCell"
    
    private var circles : [myCircle] = {
        let pok1 = myCircle(name: "Bl4")
        let pok2 = myCircle(name: "Bl3")
        let pok3 = myCircle(name: "Bli")
        let pok4 = myCircle(name: "Bl0")
        let pok5 = myCircle(name: "Lal")
        let pok6 = myCircle(name: "Te")
        let pok7 = myCircle(name: "wTW")
        let pok8 = myCircle(name: "H3RHG")
        return [pok1, pok2, pok3, pok4, pok5, pok6, pok7, pok8]
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = #colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1)
        
        view.addSubview(circleCollectionView)
        configureCollectionView()
        
        print(circles.count)
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        configureCollectionViewLayoutItemSize()
    }
    
    func configureCollectionView(){
        
        layout.scrollDirection = .horizontal
        layout.itemSize = CGSize(width: cellWidth, height: cellHeight)
        layout.minimumLineSpacing = cellSpacing
        
        //set layout
        circleCollectionView.setCollectionViewLayout(layout, animated: true)
        //set delegates
        setTableViewDelegates()
        //register cells
        circleCollectionView.register(CircleCollectionViewCell.self, forCellWithReuseIdentifier: collectionViewCellIdentifier)
        //set contraits
        circleCollectionView.translatesAutoresizingMaskIntoConstraints = false
        circleCollectionView.heightAnchor.constraint(equalToConstant: cellHeight).isActive = true
        circleCollectionView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
        circleCollectionView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        circleCollectionView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        
        circleCollectionView.backgroundColor = .clear
        circleCollectionView.decelerationRate = .fast


        
    }
    
    func calculateSectionInset() -> CGFloat { // should be overridden
        let viewWith = UIScreen.main.bounds.size.width
        return ( viewWith - cellWidth ) / 2
    }
    private func configureCollectionViewLayoutItemSize() {
        let inset: CGFloat = calculateSectionInset()
        layout.sectionInset = UIEdgeInsets(top: 0, left: inset, bottom: 0, right: inset)

        layout.itemSize = CGSize(width: layout.collectionView!.frame.size.width - inset * 2,
                                 height: layout.collectionView!.frame.size.height)
    }

}

extension FlowViewController: UICollectionViewDelegate,UICollectionViewDataSource{
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        circles.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: collectionViewCellIdentifier, for: indexPath) as? CircleCollectionViewCell else {
            fatalError("Bad instance of FavoritesCollectionViewCell")
        }
        cell.nameLabel.text = circles[indexPath.row].name
        return cell
    }
    
    
    func setTableViewDelegates(){
        circleCollectionView.delegate = self
        circleCollectionView.dataSource = self
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        print(circles.count)
    }
    


}

extension FlowViewController: UICollectionViewDelegateFlowLayout {
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let width = cellWidth
        let height = cellHeight
        
         return CGSize(width: width, height: height)
     }
    



}
class myCarouselFlowLayout: UICollectionViewFlowLayout,UICollectionViewDelegateFlowLayout {
    
    private let fadeFactor: CGFloat = 0.5
    
    override var collectionViewContentSize: CGSize {
        super.collectionViewContentSize
    }
    
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        super.layoutAttributesForElements(in: rect)?.map {
            self.layoutAttributesForItem(at: $0.indexPath)!
        }
    }
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        guard let superValue = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else { return nil }
        
        guard let cv = collectionView else { return nil }
        let collectionMidPoint = CGPoint(x: cv.bounds.midX, y: cv.bounds.midY)
        let itemMidPoint = superValue.center

        let distance = abs(itemMidPoint.x - collectionMidPoint.x)

        if distance > 100 {
            UIView.animate(withDuration: 0.2) {
                superValue.alpha = self.fadeFactor
                superValue.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
                cv.layoutIfNeeded()
            }
        }
        return superValue
    }
    
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        true
    }

    var velocityThresholdPerPage: CGFloat = 2
    
    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
            guard let collectionView = collectionView else { return proposedContentOffset }
            
            let pageLength: CGFloat
            let approxPage: CGFloat
            let currentPage: CGFloat
            
                pageLength = (self.itemSize.width + self.minimumLineSpacing)
                approxPage = collectionView.contentOffset.x / pageLength
                
        
            currentPage = round(approxPage)
            
            if velocity.x == 0 {
                return CGPoint(x: currentPage * pageLength, y: 0)
            }
            
            var nextPage: CGFloat = currentPage + (velocity.x > 0 ? 1 : -1)
            
            let increment = velocity.x / velocityThresholdPerPage
            
        nextPage += round(increment)
            
            
        return CGPoint(x: nextPage * pageLength, y: 0)
}



Please give me some advice on where and how should I use animations on it? Should I do it with UIView.animate or something else? I know that I need to use layoutIfNeeded() to animate constraints, but it doesn't work with it.

0

There are 0 best solutions below