使用 UIKit Dynamics 时如何更新 UIView 布局?

How do you update UIView layout when using UIKit Dynamics?

我有一个自定义 UIView,它使用 UIKit Dynamics 在用户点击按钮时执行动画。有问题的视图是一个简单的视图,我在 layoutSubviews() 中手动布局。然而,当 UIKit Dynamics 正在运行时,每个动画帧都会调用 layoutSubviews(),并且我在那段时间所做的任何布局更改(例如,响应更高的状态栏)都会导致我的动态视图失真。

如何在 UIKit Dynamics 动画进行时响应视图大小的变化?

更新

我创建了一个演示项目(它非常符合我的用例,尽管它被精简了),并发布了它 on GitHub。故事板使用 AutoLayout,但视图选择退出 AutoLayout 以使用 translatesAutoresizingMaskIntoConstraints = false 布局自己的子视图。要重现该行为,请在模拟器中 运行(我选择 iPhone 5),然后在星星摆动时按 ⌘Y 以见证失真。这是查看代码:

import UIKit

class CustomView: UIView {

    var swingingView: UIView!

    var animator: UIDynamicAnimator!
    var attachment: UIAttachmentBehavior!

    var lastViewFrame = CGRectZero


    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        self.translatesAutoresizingMaskIntoConstraints = false

        swingingView = UIImageView(image: UIImage(named: "Star"))
        self.addSubview(swingingView)
    }

    override func layoutSubviews() {
        // Don't run for every frame of the animation. Only when responding to a layout change
        guard self.frame != lastViewFrame else {
            return
        }

        lastViewFrame = self.frame

        swingingView.frame = CGRect(x: 0, y: self.frame.size.height / 2, width: 100, height: 100)

        // Only run this setup code once
        if animator == nil {
            animator = UIDynamicAnimator(referenceView: self)

            let gravity = UIGravityBehavior(items: [swingingView])
            gravity.magnitude = 1.5
            animator.addBehavior(gravity)

            attachment = UIAttachmentBehavior(item: swingingView,
                offsetFromCenter: UIOffset(horizontal: 0, vertical: swingingView.frame.size.height / -2),
                attachedToAnchor: CGPoint(x: self.bounds.size.width / 2, y: 0))
            attachment.length = CGFloat(250.0)
            animator.addBehavior(attachment)
        }

        animator.updateItemUsingCurrentState(swingingView)
    }
}

您应该根据 UIDynamicAnimator class reference

使用 func updateItemUsingCurrentState(_ item: UIDynamicItem)

A dynamic animator automatically reads the initial state (position and rotation) of each dynamic item you add to it, and then takes responsibility for updating the item’s state. If you actively change the state of a dynamic item after you’ve added it to a dynamic animator, call this method to ask the animator to read and incorporate the new state.

问题是您正在重新设置已旋转(即应用了变换)的视图的 frameframe 是父上下文中视图的大小。因此您无意中更改了此图像视图的 bounds

由于您使用的是 .ScaleToFill 的默认内容模式,因此当图像视图的 bounds 发生变化时,星星会(进一步)扭曲. (请注意,图像不是正方形的,所以我个人会使用 .ScaleAspectFit,但这取决于您。)

无论如何,您应该能够通过 (a) 在首次将 UIImageView 添加到视图层次结构时设置 frame 来解决此问题; (b) 不要更改 layoutSubviews 中的 frame,而只是调整图像视图的 center