iosswiftcaanimationcakeyframeanimation

How to set path in CAKeyframAnimation and change cell's hierarch?


I want to achieve the effect like this: I click cell and the button's alpha become to 0, then enlarge the cell and move to center of Screen. enter image description here And Here is my code and gif:

**// This is my VC**
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }

    private lazy var collectionView: UICollectionView = {
        let flowLayout = UICollectionViewFlowLayout()
        flowLayout.scrollDirection = .vertical
        flowLayout.itemSize = CGSize(width: 152, height: 212)
        flowLayout.sectionInset = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
        flowLayout.minimumLineSpacing = 24
        flowLayout.minimumInteritemSpacing = 16
        
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
        collectionView.register(ListViewControllerCell.self, forCellWithReuseIdentifier: "ListCell")
        collectionView.dataSource = self
        collectionView.delegate = self
        collectionView.decelerationRate = .fast
        collectionView.showsHorizontalScrollIndicator = false
        collectionView.showsVerticalScrollIndicator = false
        collectionView.clipsToBounds = false
        collectionView.alwaysBounceVertical = true
        return collectionView
    }()
}

extension ViewController {
    private func setupUI() {
        view.addSubview(collectionView)
        
        collectionView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
    }
}

extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 10
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ListCell", for: indexPath)
        
        return cell
    }
}

class ListViewControllerCell: UICollectionViewCell {
    static let identifier = "ListCell"
    let screenWidth = UIScreen.main.bounds.width
    let screenHeight = UIScreen.main.bounds.height

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
    }
    
    private lazy var pannelView: UIView = {
        let view = UIView()
        view.layer.cornerRadius = 8
        view.backgroundColor = .yellow
        return view
    }()
    
    private lazy var clickButton: UIButton = {
        let button = UIButton()
        button.layer.cornerRadius = 11
        button.contentEdgeInsets = .init(top: 0, left: 8, bottom: 0, right: 8)
        button.setTitle("Click Me!!", for: .normal)
        button.setTitleColor(UIColor.blue, for: .normal)
        button.backgroundColor = .white
        button.addTarget(self, action: #selector(onClickButton), for: .touchUpInside)
        return button
    }()
    
    private func setupUI() {
        contentView.addSubview(pannelView)
        contentView.addSubview(clickButton)
        
        pannelView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        
        clickButton.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.bottom.equalToSuperview().inset(16)
            make.height.equalTo(22)
        }
    }
}

extension ListViewControllerCell {
    @objc private func onClickButton() {
        // 1: set button alpha to 0.
        let buttonOpacity = CABasicAnimation(keyPath: "opacity")
        buttonOpacity.toValue = 1
        buttonOpacity.fromValue = 0
        buttonOpacity.duration = 0.4
        buttonOpacity.fillMode = .forwards
        buttonOpacity.isRemovedOnCompletion = false
        
        // 2: enlarge cell and set it position to center.
        let cellEnlargeTransition = CABasicAnimation(keyPath: "transform.scale")
        cellEnlargeTransition.fromValue = 1
        cellEnlargeTransition.toValue = 2
        cellEnlargeTransition.beginTime = CACurrentMediaTime() + 0.4
        cellEnlargeTransition.duration = 1
        cellEnlargeTransition.fillMode = .forwards
        cellEnlargeTransition.isRemovedOnCompletion = false
        
        let cellEnlargePosition = CAKeyframeAnimation(keyPath: "position")
        let path = CGMutablePath()
        path.move(to: CGPoint(x: contentView.frame.minX, y: contentView.frame.minY))
        let toPositonX = (screenWidth - contentView.bounds.width)/2
        let toPositionY = (screenWidth - contentView.bounds.height)/2
        path.addLine(to: CGPoint(x: toPositonX, y: toPositionY))
        cellEnlargePosition.path = path
        cellEnlargePosition.beginTime = CACurrentMediaTime() + 0.4
        cellEnlargeTransition.duration = 1
        cellEnlargeTransition.fillMode = .forwards
        cellEnlargePosition.isRemovedOnCompletion = false
        
        CATransaction.begin()
        clickButton.layer.add(buttonOpacity, forKey: nil)
        contentView.layer.add(cellEnlargeTransition, forKey: nil)
        contentView.layer.add(cellEnlargePosition, forKey: nil)
        CATransaction.commit()
    }
}

I have couple of questions: 1.The beginning position is not right, the transition animation should start at cell's originX and originY. 2.I want to keep button's alpha to zero, but it comes to 1 again when the "opacity" animation is over. 3.The ending position is also not right, I want to keep it at center of the screen. 4.I want the cell hierarchy to be higher than the others which do not execute animation. enter image description here


Solution

  • The trouble (in addition to the fact that your whole approach to this animation is wrong) is that this is a collection view. To make a collection view cell actually appear in front of all the others, you would need to write a custom collection view layout — and you are not prepared to do that. And it wouldn't work in any case, because the whole collection view would still be scrollable, and then where would you be?

    Instead, I suggest revising your design. Just use an interface like Apple's Photos app, or Calendar app. In Photos, for example, you see a grid of photos; you tap one and it zooms to fill the screen. That is actually a navigation view holding a view controller showing a collection view, pushed with a custom transition animation to a view controller holding the single photo, and is quite easy to do.

    Alternatively I think you could quite readily do this as a collection view and a presented view controller with a transparent background showing the single "cell". The animation you see me doing here is quite analogous.