Home > database >  iOS - Exchanging position of two UIViews inside Animation block
iOS - Exchanging position of two UIViews inside Animation block

Time:11-16

I've been having a strong headache with this problem, because I know for a fact should be a simple solution but I can't find the right answer.

The idea is that I have two UIViews (let's call them A and B). All I want to do is to swap their positions on button press, by the A view going down to B position, and B view going up to A position.

Here's what I'm trying to achieve.

I'm trying to achieve that by just swapping the .center of the elements that I need (as A and B are both UIViews that cointains a bunch of elements inside them). This is inside an animation block - UIView.animate

However, what is currently happening is that the animation is performing in exactly the opposite way: The A view would go up the screen, then re-appear in the bottom side of the screen and end up in the desired position (B initial position). Same thing with the B view, it's currently going all the way to the bottom of the screen, then re-appear at the top, and finish the animation in the desired position (A initial position).

This is what is currently happening.

This is my code so far (note that ownAccountTransferView is defined in another file, and below code is placed in my UIViewController that stores that view). I call this function on button press, after swapping the the data (like labels and such) between the two cards.

fileprivate func performSwitchAnimation() {

// NOTE: ownAccountTransferView is a reference to my view, think of it like self.view
        
 let fromAccountCardCenter = 
 self.ownAccountTransferView.fromAccountCardView.center
 let fromAccountTypeLabelCenter = 
 self.ownAccountTransferView.fromAccountTypeLabel.center
 let fromAccountTypeViewCenter = 
 self.ownAccountTransferView.fromAccountTypeView.center
 let fromAccountBalanceLabelCenter = 
 self.ownAccountTransferView.fromAccountBalanceLabel.center
 let fromAccountNameLabelCenter = 
 self.ownAccountTransferView.fromAccountNameLabel.center
 let fromAccountChevronCenter = 
 self.ownAccountTransferView.fromAccountChevronView.center
        
 let toAccountCardCenter = 
 self.ownAccountTransferView.toAccountCardView.center
 let toAccountTypeLabelCenter = 
 self.ownAccountTransferView.toAccountTypeLabel.center
 let toAccountTypeViewCenter = 
 self.ownAccountTransferView.toAccountTypeView.center
 let toAccountBalanceLabelCenter = 
 self.ownAccountTransferView.toAccountBalanceLabel.center
 let toAccountNameLabelCenter = 
 self.ownAccountTransferView.toAccountNameLabel.center
 let toAccountChevronCenter = 
 self.ownAccountTransferView.toAccountChevronView.center
        
  UIView.animate(withDuration: 1, delay: 0, options: []) {
            
   self.ownAccountTransferView.switchAccountsButton.isUserInteractionEnabled = false
            
       self.ownAccountTransferView.fromAccountCardView.center = toAccountCardCenter
       self.ownAccountTransferView.fromAccountTypeLabel.center = toAccountTypeLabelCenter
       self.ownAccountTransferView.fromAccountTypeView.center = toAccountTypeViewCenter
            self.ownAccountTransferView.fromAccountBalanceLabel.center = toAccountBalanceLabelCenter
       self.ownAccountTransferView.fromAccountNameLabel.center = toAccountNameLabelCenter
       self.ownAccountTransferView.fromAccountChevronView.center = toAccountChevronCenter
            
       self.ownAccountTransferView.toAccountCardView.center = fromAccountCardCenter
       self.ownAccountTransferView.toAccountTypeLabel.center = fromAccountTypeLabelCenter
       self.ownAccountTransferView.toAccountTypeView.center = fromAccountTypeViewCenter
       self.ownAccountTransferView.toAccountBalanceLabel.center = fromAccountBalanceLabelCenter
       self.ownAccountTransferView.toAccountNameLabel.center = fromAccountNameLabelCenter
       self.ownAccountTransferView.toAccountChevronView.center = fromAccountChevronCenter
            
        } completion: { isDone in
            if isDone {
                self.ownAccountTransferView.switchAccountsButton.isUserInteractionEnabled = true
            }
        }
        
    }

So, how can I make the animation work the way I want - A view going to the bottom and B view going to the top?

Thanks in advance.

UPDATE: I fixed it by ussing transform. Tried messing around with animating constraints A LOT, tried every possible interaction between top/bottom constraints, but it would either not do anything at all or just bug my view and send it to hell. I even was insisting with animating constraints in support calls with my coworkers. And so were they. However, after googling despair measures, I ended up using viewController.myView.myLabel.transform = CGAffineTransform(translationX: 0, y: 236).

Of course, you can reduce this to: view.transform = CGAffineTransform(translationX: 0, y: 236). The value is arbitrary, and I'm not sure if it can some day break or cause undesired behaviour. I tested it on iPhone 8 and iPhone 13, and in both the animations performs just ok (A view goes down, B goes up, click switch and do the opposite, etc...).

Side note: If you gonna use this, you need to NOT copy/paste the values when you need your views to "come back" to where they were at the beginning. I used 236 and -236 for A and B respectively, but to restore them I need to use -2 and 2 or it super bugs out. Idk why tho. But it works! Thanks all

CodePudding user response:

Enclose both the views in a parent view and do the following:

if let firstIndex = view.subviews.firstIndex(of: view1), let secondIndex = view.subviews.firstIndex(of: view2) {
        let firstViewFrame = view1.frame

        UIView.animate(withDuration: 1, animations: {
            self.view1.frame = self.view2.frame
            self.view2.frame = firstViewFrame
            self.view.exchangeSubview(at: firstIndex, withSubviewAt: secondIndex)
        })
    }

Check out the answer here.

EDIT: I was able to achieve the said animation using Autolayout constraints. Basically what I did was have fixed constraints for two views initially. And have two sets of changing constraints - startConstraints and endConstraints. Simply activating and deactivating the constraints did the job. Check out the demo. Here's my code:

class ViewController: UIViewController {
var view1: UIView = {
    let v = UIView()
    v.backgroundColor = .red
    return v
}()

var view2: UIView = {
    let v = UIView()
    v.backgroundColor = .blue
    return v
}()

var button: UIButton = {
    let b = UIButton()
    b.setTitle("Animate", for: .normal)
    b.setTitleColor(.black, for: .normal)
    return b
}()

var fixedConstraints: [NSLayoutConstraint]!
var startConstraints: [NSLayoutConstraint]!
var endConstraints: [NSLayoutConstraint]!

override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(view1)
    view.addSubview(view2)
    view.addSubview(button)
    
    view1.translatesAutoresizingMaskIntoConstraints = false
    view2.translatesAutoresizingMaskIntoConstraints = false
    button.translatesAutoresizingMaskIntoConstraints = false
    
    fixedConstraints = [
        button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        view1.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        view2.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        view1.heightAnchor.constraint(equalToConstant: 100),
        view1.widthAnchor.constraint(equalToConstant: 100),
        view2.heightAnchor.constraint(equalToConstant: 100),
        view2.widthAnchor.constraint(equalToConstant: 100),
    ]
    
    startConstraints = [
        view1.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0),
        view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: view2.bottomAnchor),
    ]
    
    startConstraints.append(contentsOf: fixedConstraints)
    NSLayoutConstraint.activate(startConstraints)
    
    button.addTarget(self, action: #selector(animateSwitchingOfViews), for: .touchUpInside)
}

@objc
private func animateSwitchingOfViews(){
    endConstraints = [
        view2.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0),
        view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo:view1.bottomAnchor)
    ]
    endConstraints.append(contentsOf: fixedConstraints)
    
    UIView.animate(withDuration: 2, animations: {
        NSLayoutConstraint.deactivate(self.startConstraints)
        NSLayoutConstraint.activate(self.endConstraints)
        self.view.layoutIfNeeded()
    })
}

}

CodePudding user response:

UPDATE: I fixed it by ussing transform. Tried messing around with animating constraints A LOT, tried every possible interaction between top/bottom constraints, but it would either not do anything at all or just bug my view and send it to hell. I even was insisting with animating constraints in support calls with my coworkers. And so were they. However, after googling despair measures, I ended up using viewController.myView.myLabel.transform = CGAffineTransform(translationX: 0, y: 236).

Of course, you can reduce this to: view.transform = CGAffineTransform(translationX: 0, y: 236). The value is arbitrary, and I'm not sure if it can some day break or cause undesired behaviour. I tested it on iPhone 8 and iPhone 13, and in both the animations performs just ok (A view goes down, B goes up, click switch and do the opposite, etc...).

Side note: If you gonna use this, you need to NOT copy/paste the values when you need your views to "come back" to where they were at the beginning. I used 236 and -236 for A and B respectively, but to restore them I need to use -2 and 2 or it super bugs out. Idk why tho. But it works! Thanks all

  • Related