如何让带Auto Layout约束的Spring Animations表现符合预期?
问题描述
我正尝试让Spring Animations正常工作,并创建了Git仓库。红视图(brokenView)的约束设置如下:
let ac = brokenView.widthAnchor.constraint(equalTo: brokenView.heightAnchor, multiplier: 9/16) let xc = brokenView.centerXAnchor.constraint(equalTo: animView.centerXAnchor) let yc = brokenView.centerYAnchor.constraint(equalTo: animView.centerYAnchor) let widthC = brokenView.widthAnchor.constraint(equalTo: animView.widthAnchor) widthC.priority = .defaultLow let gewc = brokenView.widthAnchor.constraint(greaterThanOrEqualTo: animView.widthAnchor) let geHC = brokenView.heightAnchor.constraint(greaterThanOrEqualTo: animView.heightAnchor) geHC.priority = .required
蓝色视图(animView)初始宽高比不等于9/16,会通过动画过渡至9/16。我期望当蓝色视图过高时,红视图能随之变宽,但实际红视图仅遵循低优先级的宽度约束。恳请提供相关建议。
缺少storyboard文件的完整代码:
import UIKit class MyViewController: UIViewController { @IBOutlet weak var animView: UIView! @IBOutlet weak var brokenView: UIView! var isBig = false override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. brokenView.border(3, color: .red) animView.border(3, color: .blue) animView.frame = CGRect(x: view.frame.midX - 120, y: view.frame.midY - 180, width: 240, height: 360) brokenView.translatesAutoresizingMaskIntoConstraints = false let ac = brokenView.widthAnchor.constraint(equalTo: brokenView.heightAnchor, multiplier: 9/16) let xc = brokenView.centerXAnchor.constraint(equalTo: animView.centerXAnchor) let yc = brokenView.centerYAnchor.constraint(equalTo: animView.centerYAnchor) let widthC = brokenView.widthAnchor.constraint(equalTo: animView.widthAnchor) widthC.priority = .defaultLow let gewc = brokenView.widthAnchor.constraint(greaterThanOrEqualTo: animView.widthAnchor) let geHC = brokenView.heightAnchor.constraint(greaterThanOrEqualTo: animView.heightAnchor) geHC.priority = .required NSLayoutConstraint.activate([ ac, xc, yc, widthC, gewc, geHC ]) let gr = UITapGestureRecognizer(target: self, action: #selector(MyViewController.handleTap(_:))) view.addGestureRecognizer(gr) } @objc func handleTap(_ gr: UITapGestureRecognizer) { let newFrame: CGRect if isBig { newFrame = CGRect(x: view.frame.midX - 120, y: view.frame.midY - 180, width: 240, height: 360) } else { newFrame = CGRect(x: view.frame.midX - (270/2), y: view.frame.midY - (480/2), width: 270, height: 480) } let timing = UISpringTimingParameters.init(dampingRatio: 0.01, frequencyResponse: 10) let anim = UIViewPropertyAnimator(duration: 10, timingParameters: timing) anim.addAnimations { self.animView.frame = newFrame } anim.addCompletion { _ in self.isBig = !self.isBig } anim.startAnimation() } } extension UIView { func border(_ width: Float, color: UIColor) { layer.borderColor = color.cgColor layer.borderWidth = CGFloat(width) } } extension UISpringTimingParameters { public convenience init(dampingRatio: CGFloat, frequencyResponse: CGFloat) { let mass = 1 as CGFloat let stiffness = pow(2 * .pi / frequencyResponse, 2) * mass let damping = 4 * .pi * dampingRatio * mass / frequencyResponse self.init(mass: mass, stiffness: stiffness, damping: damping, initialVelocity: .zero) } }
效果演示:
解决方案
核心问题分析
- 约束逻辑冲突:同时设置
width >= animView.width、height >= animView.height和固定9/16宽高比时,Auto Layout无法满足所有条件,会优先保留低优先级的等宽约束而非让红视图变宽。 - frame动画的缺陷:直接修改
animView.frame会绕过Auto Layout系统,导致brokenView的约束无法正确响应蓝视图的尺寸变化,动画过程中布局会出现冲突。
具体修复步骤
重构约束逻辑:
- 移除低优先级的等宽约束
widthC,因为width >= animView.width已经能保证红视图宽度不小于蓝视图。 - 降低红视图宽高比约束的优先级,让尺寸约束优先生效:
let ac = brokenView.widthAnchor.constraint(equalTo: brokenView.heightAnchor, multiplier: 9/16) ac.priority = .defaultHigh // 让尺寸约束优先于宽高比约束 - 保留
centerX、centerY、width >= animView.width、height >= animView.height(required优先级)这几个约束。
- 移除低优先级的等宽约束
改用约束动画替代frame动画:
给蓝视图添加宽高约束,通过修改约束常量实现动画,确保Auto Layout能同步驱动红视图布局:- 在类中声明约束属性:
private var animViewWidthConstraint: NSLayoutConstraint! private var animViewHeightConstraint: NSLayoutConstraint! - 在
viewDidLoad中替换frame设置为约束:animView.translatesAutoresizingMaskIntoConstraints = false animView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true animView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true animViewWidthConstraint = animView.widthAnchor.constraint(equalToConstant: 240) animViewHeightConstraint = animView.heightAnchor.constraint(equalToConstant: 360) NSLayoutConstraint.activate([animViewWidthConstraint, animViewHeightConstraint]) - 修改tap事件的动画代码:
@objc func handleTap(_ gr: UITapGestureRecognizer) { let newWidth: CGFloat = isBig ? 240 : 270 let newHeight: CGFloat = isBig ? 360 : 480 let timing = UISpringTimingParameters(dampingRatio: 0.01, frequencyResponse: 10) let anim = UIViewPropertyAnimator(duration: 10, timingParameters: timing) anim.addAnimations { self.animViewWidthConstraint.constant = newWidth self.animViewHeightConstraint.constant = newHeight self.view.layoutIfNeeded() // 触发Auto Layout更新 } anim.addCompletion { _ in self.isBig = !self.isBig } anim.startAnimation() }
- 在类中声明约束属性:
修复后效果
调整后,蓝视图尺寸变化时,红视图会自动适配:
- 当蓝视图宽高比小于9/16(过高),红视图会保持9/16比例,同时宽度和高度均不小于蓝视图。
- 动画过程中Auto Layout能正确同步两个视图的布局,不会出现约束冲突或异常。
内容的提问来源于stack exchange,提问作者nickneedsaname
相关产品推荐
相关产品推荐

