You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS:如何在UIView动画块中正确交换两个UIView的位置?

交换两个UIView位置的动画异常问题解决

问题描述

需求是点击按钮时让两个UIView(A为fromAccountCardView、B为toAccountCardView)交换位置:A向下移动到B的初始位置,B向上移动到A的初始位置。

直接在UIView.animate块中交换两个视图及其内部子视图的.center属性后,动画效果完全不符合预期:A会先向上移出屏幕,再从底部出现到达目标位置;B则先向下移出屏幕,再从顶部出现到达目标位置。

原实现代码:

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
        }
    }
}

问题根源

直接修改.center属性的做法,在Auto Layout布局环境下会触发约束冲突——视图的实际位置由约束控制,手动修改center后,Auto Layout会在动画过程中试图修正位置,导致动画路径出现异常的“绕路”情况。同时逐个修改子视图的center不仅冗余,还容易造成动画不同步。

可行解决方案

方案1:使用Transform实现(已验证有效)

通过CGAffineTransform的平移动画,绕过Auto Layout的约束干扰,直接控制视图的视觉位置,动画完成后再重置transform并交换数据:

fileprivate func performSwitchAnimation() {
    let ownView = self.ownAccountTransferView
    ownView.switchAccountsButton.isUserInteractionEnabled = false
    
    // 计算两个视图的垂直偏移量:from需要向下移动的距离
    let offsetY = ownView.toAccountCardView.frame.minY - ownView.fromAccountCardView.frame.minY
    
    UIView.animate(withDuration: 1, delay: 0, options: .curveEaseInOut) {
        // from向下平移,to向上平移
        ownView.fromAccountCardView.transform = CGAffineTransform(translationX: 0, y: offsetY)
        ownView.toAccountCardView.transform = CGAffineTransform(translationX: 0, y: -offsetY)
    } completion: { [weak self] isDone in
        guard let self = self else { return }
        let ownView = self.ownAccountTransferView
        // 重置transform,同时交换两个视图的业务数据
        ownView.fromAccountCardView.transform = .identity
        ownView.toAccountCardView.transform = .identity
        
        // 此处替换为实际的数据交换逻辑
        let tempAccountData = ownView.fromAccountData
        ownView.fromAccountData = ownView.toAccountData
        ownView.toAccountData = tempAccountData
        
        ownView.switchAccountsButton.isUserInteractionEnabled = true
    }
}

注意:transform会作用于整个视图及其所有子视图,无需单独给子视图加动画;恢复位置时必须用.identity重置,不能复用原偏移值,否则会出现异常。

方案2:Auto Layout约束动画(适合依赖约束的场景)

如果必须基于Auto Layout实现,要通过修改约束的constant值而非直接改center:

  1. 给两个视图的垂直位置约束(如centerY或top约束)创建IBOutlet
  2. 交换约束的constant值后,在动画块中调用layoutIfNeeded()触发动画

示例代码:

// 假设已创建两个约束的IBOutlet
@IBOutlet weak var fromCardCenterYConstraint: NSLayoutConstraint!
@IBOutlet weak var toCardCenterYConstraint: NSLayoutConstraint!

fileprivate func performSwitchAnimation() {
    let ownView = self.ownAccountTransferView
    ownView.switchAccountsButton.isUserInteractionEnabled = false
    
    // 交换两个约束的constant值
    let tempConstant = fromCardCenterYConstraint.constant
    fromCardCenterYConstraint.constant = toCardCenterYConstraint.constant
    toCardCenterYConstraint.constant = tempConstant
    
    UIView.animate(withDuration: 1, delay: 0, options: .curveEaseInOut) {
        ownView.layoutIfNeeded()
    } completion: { isDone in
        ownView.switchAccountsButton.isUserInteractionEnabled = true
        // 交换业务数据
    }
}

注意:要确保没有其他垂直约束干扰视图位置,否则仍可能出现动画异常。

总结

直接修改center属性在Auto Layout环境下极易触发约束冲突,导致动画路径异常。使用CGAffineTransform平移是更简洁可靠的方案;如果依赖Auto Layout,必须通过修改约束并调用layoutIfNeeded()来实现动画。

内容的提问来源于stack exchange,提问作者Pablo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 07:10:31