将动态动画位置更改映射到边界

此示例显示如何自定义 UIDynamicItem 协议以映射动态动画视图的位置更改以限制更改以创建以弹性方式扩展和收缩的 UIButton

https://i.stack.imgur.com/9R0zL.gif

首先,我们需要创建一个实现 UIDynamicItem 的新协议,但它也具有可设置且可获取的 bounds 属性。

迅速

protocol ResizableDynamicItem: UIDynamicItem
{
    var bounds: CGRect { set get }
}
extension UIView: ResizableDynamicItem {}

Objective-C

@protocol ResizableDynamicItem <UIDynamicItem>
@property (nonatomic, readwrite) CGRect bounds;
@end

然后我们将创建一个包装器对象,它将包裹 UIDynamicItem,但会将中心更改映射到项目的宽度和高度。我们还将为基础项目的 boundstransform 提供 passthrough。这将导致动态动画制作者对中心的任何更改 x 和底层项目的 y 值将应用于项目的宽度和高度。

迅速

final class PositionToBoundsMapping: NSObject, UIDynamicItem
{
    var target: ResizableDynamicItem
    
    init(target: ResizableDynamicItem)
    {
        self.target = target
        super.init()
    }
    
    var bounds: CGRect
    {
        get
        {
            return self.target.bounds
        }
    }
    
    var center: CGPoint
    {
        get
        {
            return CGPoint(x: self.target.bounds.width, y: self.target.bounds.height)
        }
        
        set
        {
            self.target.bounds = CGRect(x: 0.0, y: 0.0, width: newValue.x, height: newValue.y)
        }
    }
    
    var transform: CGAffineTransform
    {
        get
        {
            return self.target.transform
        }
        
        set
        {
            self.target.transform = newValue
        }
    }
}

Objective-C

@interface PositionToBoundsMapping ()
@property (nonatomic, strong) id<ResizableDynamicItem> target;
@end

@implementation PositionToBoundsMapping

- (instancetype)initWithTarget:(id<ResizableDynamicItem>)target
{
    self = [super init];
    if (self)
    {
        _target = target;
    }
    return self;
}

- (CGRect)bounds
{
    return self.target.bounds;
}

- (CGPoint)center
{
    return CGPointMake(self.target.bounds.size.width, self.target.bounds.size.height);
}

- (void)setCenter:(CGPoint)center
{
    self.target.bounds = CGRectMake(0, 0, center.x, center.y);
}

- (CGAffineTransform)transform
{
    return self.target.transform;
}

- (void)setTransform:(CGAffineTransform)transform
{
    self.target.transform = transform;
}

@end

最后,我们将创建一个有一个按钮的 UIViewController。按下按钮后,我们将创建 PositionToBoundsMapping,按钮作为包装的动态项目。我们创建一个 UIAttachmentBehavior 到它的当前位置然后添加一个瞬时 UIPushBehavior 到它。但是因为我们已经映射了更改其边界,所以按钮不会移动,而是会增长和缩小。

迅速

final class ViewController: UIViewController
{
    lazy var button: UIButton =
    {
        let button = UIButton(frame: CGRect(x: 0.0, y: 0.0, width: 300.0, height: 200.0))
        button.backgroundColor = .red
        button.layer.cornerRadius = 15.0
        button.setTitle("Tap Me", for: .normal)
        self.view.addSubview(button)
        return button
    }()
    
    var buttonBounds = CGRect.zero
    var animator: UIDynamicAnimator?
    
    override func viewDidLoad() 
    {
        super.viewDidLoad()
        view.backgroundColor = .white
        button.addTarget(self, action: #selector(self.didPressButton(sender:)), for: .touchUpInside)
        buttonBounds = button.bounds
    }
    
    override func viewDidLayoutSubviews() 
    {
        super.viewDidLayoutSubviews()
        button.center = view.center
    }
    
    func didPressButton(sender: UIButton)
    {
        // Reset bounds so if button is press twice in a row, previous changes don't propogate
        button.bounds = buttonBounds
        let animator = UIDynamicAnimator(referenceView: view)
        
        // Create mapping
        let buttonBoundsDynamicItem = PositionToBoundsMapping(target: button)
        
        // Add Attachment behavior
        let attachmentBehavior = UIAttachmentBehavior(item: buttonBoundsDynamicItem, attachedToAnchor: buttonBoundsDynamicItem.center)
        
        // Higher frequency faster oscillation
        attachmentBehavior.frequency = 2.0
        
        // Lower damping longer oscillation lasts
        attachmentBehavior.damping = 0.1
        animator.addBehavior(attachmentBehavior)
        
        let pushBehavior = UIPushBehavior(items: [buttonBoundsDynamicItem], mode: .instantaneous)
        
        // Change angle to determine how much height/ width should change 45° means heigh:width is 1:1
        pushBehavior.angle = .pi / 4.0
        
        // Larger magnitude means bigger change
        pushBehavior.magnitude = 30.0
        animator.addBehavior(pushBehavior)
        pushBehavior.active = true
        
        // Hold refrence so animator is not released
        self.animator = animator
    }
}

Objective-C

@interface ViewController ()
@property (nonatomic, strong) UIButton *button;
@property (nonatomic, assign) CGRect buttonBounds;
@property (nonatomic, strong) UIDynamicAnimator *animator;
@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    [self.button addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventTouchUpInside];
    self.buttonBounds = self.button.bounds;
}

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    self.button.center = self.view.center;
}

- (UIButton *)button
{
    if (!_button)
    {
        _button = [[UIButton alloc]initWithFrame:CGRectMake(0.0, 0.0, 200.0, 200.0)];
        _button.backgroundColor = [UIColor redColor];
        _button.layer.cornerRadius = 15.0;
        [_button setTitle:@"Tap Me" forState:UIControlStateNormal];
        [self.view addSubview:_button];
    }
    return _button;
}

- (void)didTapButton:(id)sender
{
    self.button.bounds = self.buttonBounds;
    UIDynamicAnimator *animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    PositionToBoundsMapping *buttonBoundsDynamicItem = [[PositionToBoundsMapping alloc]initWithTarget:sender];
    UIAttachmentBehavior *attachmentBehavior = [[UIAttachmentBehavior alloc]initWithItem:buttonBoundsDynamicItem attachedToAnchor:buttonBoundsDynamicItem.center];
    [attachmentBehavior setFrequency:2.0];
    [attachmentBehavior setDamping:0.3];
    [animator addBehavior:attachmentBehavior];
    
    UIPushBehavior *pushBehavior = [[UIPushBehavior alloc] initWithItems:@[buttonBoundsDynamicItem] mode:UIPushBehaviorModeInstantaneous];
    pushBehavior.angle = M_PI_4;
    pushBehavior.magnitude = 2.0;
    [animator addBehavior:pushBehavior];
    
    [pushBehavior setActive:TRUE];
    
    self.animator = animator;
}

@end

有关更多信息,请参阅 UIKit Dynamics Catalog