0

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"

  • Do [answers of this post](https://stackoverflow.com/q/26329885/1971013) help? – meaning-matters May 19 '22 at 14:59
  • Please don't post duplicate questions (https://stackoverflow.com/questions/72303777/move-uiimage-on-top-of-another-uiimage-in-swift) ... edit your original question if comments indicate your description of the task was not clear. – DonMag May 19 '22 at 15:08
  • 1
    You need to take this step-by-step. First, have you learned how to animate a view to "fly" from one position to another? If not, search for `swift animate uiimageview position` ... you'll find hundreds (likely thousands) of examples. Next, ask yourself why you want the views in that hierarchy? Does it serve some purpose? If so, look at using a `UIView` to *hold* the "card view" ... set `Clips to Bounds` to false ... animate the actual card view from outside the "holder view" bounds to centered in its bounds. If you run into a **specific** problem, come back and ask a **specific** question. – DonMag May 19 '22 at 15:22

2 Answers2

2

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.

matt
  • 515,959
  • 87
  • 875
  • 1,141
1

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

DonMag
  • 69,424
  • 5
  • 50
  • 86