使用 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.
问题是您正在重新设置已旋转(即应用了变换)的视图的 frame
。 frame
是父上下文中视图的大小。因此您无意中更改了此图像视图的 bounds
。
由于您使用的是 .ScaleToFill
的默认内容模式,因此当图像视图的 bounds
发生变化时,星星会(进一步)扭曲. (请注意,图像不是正方形的,所以我个人会使用 .ScaleAspectFit
,但这取决于您。)
无论如何,您应该能够通过 (a) 在首次将 UIImageView
添加到视图层次结构时设置 frame
来解决此问题; (b) 不要更改 layoutSubviews
中的 frame
,而只是调整图像视图的 center
。
我有一个自定义 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.
问题是您正在重新设置已旋转(即应用了变换)的视图的 frame
。 frame
是父上下文中视图的大小。因此您无意中更改了此图像视图的 bounds
。
由于您使用的是 .ScaleToFill
的默认内容模式,因此当图像视图的 bounds
发生变化时,星星会(进一步)扭曲. (请注意,图像不是正方形的,所以我个人会使用 .ScaleAspectFit
,但这取决于您。)
无论如何,您应该能够通过 (a) 在首次将 UIImageView
添加到视图层次结构时设置 frame
来解决此问题; (b) 不要更改 layoutSubviews
中的 frame
,而只是调整图像视图的 center
。