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

如何让带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)
    }
}

效果演示:
动画异常效果


解决方案

核心问题分析

  1. 约束逻辑冲突:同时设置width >= animView.width、height >= animView.height和固定9/16宽高比时,Auto Layout无法满足所有条件,会优先保留低优先级的等宽约束而非让红视图变宽。
  2. frame动画的缺陷:直接修改animView.frame会绕过Auto Layout系统,导致brokenView的约束无法正确响应蓝视图的尺寸变化,动画过程中布局会出现冲突。

具体修复步骤

  1. 重构约束逻辑:

    • 移除低优先级的等宽约束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优先级)这几个约束。
  2. 改用约束动画替代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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 18:05:54