How to animate movement of UIView between different superViews

500 Views Asked by At

Hello I am new at swift and IOS apps , I am trying to animate movement of cardBackImage (UIImage) from deckPileImage to the card view, but everything got different superViews and I have no idea how to do it properly , all the location have different frames ( superviews as described in the Image) , Should I use CGAffineTransform ?

viewHierarchyDescription

try to imagine my abstraction as a "face down card fly from deck into its possition on boardView"

2

There are 2 best solutions below

1
On

To help get you going...

First, no idea why you have your "deckPileImage" in a stack view, but assuming you have a reason for doing so...

a simple "card" view - bordered with rounded corners

class CardView: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        layer.cornerRadius = 16
        layer.masksToBounds = true
        layer.borderWidth = 1
        layer.borderColor = UIColor.black.cgColor
    }
}

a basic view controller - adds a "deck pile view" to a stack view, and a "card position view" as the destination for the new, animated cards.

class AnimCardVC: UIViewController {
    
    let deckStackView: UIStackView = UIStackView()
    let cardPositionView: UIView = UIView()
    let deckPileView: CardView = CardView()
    
    let cardSize: CGSize = CGSize(width: 80, height: 120)
    
    // card colors to cycle through
    let colors: [UIColor] = [
        .systemRed, .systemGreen, .systemBlue,
        .systemCyan, .systemOrange,
    ]
    var colorIDX: Int = 0
    
    // card position constraints to animate
    var animXAnchor: NSLayoutConstraint!
    var animYAnchor: NSLayoutConstraint!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemBackground
        
        deckStackView.translatesAutoresizingMaskIntoConstraints = false
        deckPileView.translatesAutoresizingMaskIntoConstraints = false
        cardPositionView.translatesAutoresizingMaskIntoConstraints = false
        
        deckStackView.addArrangedSubview(deckPileView)
        view.addSubview(deckStackView)
        view.addSubview(cardPositionView)
        
        // always respect safe area
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            deckStackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            deckStackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            // we'll let the stack view subviews determine its size

            deckPileView.widthAnchor.constraint(equalToConstant: cardSize.width),
            deckPileView.heightAnchor.constraint(equalToConstant: cardSize.height),

            cardPositionView.topAnchor.constraint(equalTo: deckStackView.bottomAnchor, constant: 100.0),
            cardPositionView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            cardPositionView.widthAnchor.constraint(equalToConstant: cardSize.width + 2.0),
            cardPositionView.heightAnchor.constraint(equalToConstant: cardSize.height + 2.0),
            
        ])
        
        // outline the card holder view
        cardPositionView.backgroundColor = .systemYellow
        cardPositionView.layer.borderColor = UIColor.blue.cgColor
        cardPositionView.layer.borderWidth = 2
        
        // make the "deck card" gray to represent the deck
        deckPileView.backgroundColor = .lightGray
    }
    
    func animCard() {
        
        let card = CardView()
        card.backgroundColor = colors[colorIDX % colors.count]
        colorIDX += 1
        
        card.translatesAutoresizingMaskIntoConstraints = false
        
        card.widthAnchor.constraint(equalToConstant: cardSize.width).isActive = true
        card.heightAnchor.constraint(equalToConstant: cardSize.height).isActive = true
        
        view.addSubview(card)

        // center the new card on the deckCard
        animXAnchor = card.centerXAnchor.constraint(equalTo: deckPileView.centerXAnchor)
        animYAnchor = card.centerYAnchor.constraint(equalTo: deckPileView.centerYAnchor)
        
        // activate those constraints
        animXAnchor.isActive = true
        animYAnchor.isActive = true
        
        // run the animation *after* the card has been placed at its starting position
        DispatchQueue.main.async {
            // de-activate the current constraints
            self.animXAnchor.isActive = false
            self.animYAnchor.isActive = false
            // center the new card on the cardPositionView
            self.animXAnchor = card.centerXAnchor.constraint(equalTo: self.cardPositionView.centerXAnchor)
            self.animYAnchor = card.centerYAnchor.constraint(equalTo: self.cardPositionView.centerYAnchor)
            // re-activate those constraints
            self.animXAnchor.isActive = true
            self.animYAnchor.isActive = true
            // 1/2 second animation
            UIView.animate(withDuration: 0.5, animations: {
                self.view.layoutIfNeeded()
            })
        }

    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        animCard()
    }
    
}

It looks like this:

enter image description here

Each time you tap anywhere the code will add a new "card" and animate it from the "deck" view to the "card position" view.

enter image description here enter image description here enter image description here

0
On

Don't animate the view at all. Instead, animate a snapshot view as a proxy. You can see me doing it here, in this scene from one of my apps.

enter image description here

That red rectangle looks like it's magically flying out of one view hierarchy into another. But it isn't. In reality there are two red rectangles. I hide the first rectangle and show the snapshot view in its place, animate the snapshot view to where the other rectangle is lurking hidden, then hide the snapshot and show the other rectangle.