1 给视图添加重力效果
1.1 问题
当给某个视图加上UIGravityBehavior重力行为之后,这个视图就具有重力,会如同掉入了无底洞,不断地下坠,不断的加速,本案例使用UIGravityBehavior重力行为给imageView添加重力行为,如图-1所示:
图-1
1.2 方案
首先在创建好的Xcode项目的Storyboard中拖放一个ImageView控件和两个Button控件,在右边栏的检查器中设置好ImageView的显示图片。
将ImageView控件关联成TRViewController的属性imageView,两个按钮关联成TRViewController的动作方法start和end。
其次在TRViewController中定义两个私有属性UIDynamicAnimator类型的animator和UIGravityBehavior类型的gravityBehavior。
然后在viewDidLoad方法中创建属性animator和gravityBehavior,将self.view设置为属性animator的referenceView引用视图,给属性imageView添加重力行为。
最后实现start方法和end方法,点击start按钮imageView开始执行重力行为往下坠,点击end按钮停止重力行为。
1.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:搭建Storyboard界面
首先在创建好的Xcode项目的Storyboard中拖放一个ImageView控件和两个Button控件,在右边栏的检查器中设置好ImageView的显示图片,Storyboard中的界面如图-2所示:
图-2
将ImageView控件关联成TRViewController的属性imageView,两个按钮关联成TRViewController的动作方法start和end,代码如下所示:
- @interface TRViewController ()
- @property (weak, nonatomic) IBOutlet UIImageView *imageView;
- @end
步骤二:定义animator和gravityBehavior属性
首先在TRViewController中定义两个私有属性UIDynamicAnimator类型的animator和UIGravityBehavior类型的gravityBehavior,代码如下所示:
- @interface TRViewController ()
- @property (weak, nonatomic) IBOutlet UIImageView *imageView;
- @property (strong, nonatomic)UIDynamicAnimator *animator;
- @property (strong, nonatomic)UIGravityBehavior *gravityBehavior;
- @end
然后在viewDidLoad方法中创建属性animator和gravityBehavior,将self.view设置为属性animator的referenceView引用视图,给属性imageView添加重力行为,代码如下所示:
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- //将self.view设置为动力坐标系的参考
- UIDynamicAnimator *animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
- //给imageView添加重力行为
- UIGravityBehavior *gravity = [[UIGravityBehavior alloc]initWithItems:@[self.imageView]];
- self.gravityBehavior = gravity;
- self.animator = animator;
- }
步骤三:实现start和end方法
当点击start按钮时imageView开始执行重力行为往下坠,在start方法中使用addBehavior:方法执行重力行为。
当点击end按钮时停止重力行为,在end方法中使用removeBehavior:方法将重力行为移除,代码如下所示:
- - (IBAction)start
- {
- //当点击start按钮时将重力行为添加到self.animator
- [self.animator addBehavior:self.gravityBehavior];
- }
- - (IBAction)end
- {
- //当点击end按钮时将重力行为移除
- [self.animator removeBehavior:self.gravityBehavior];
- }
1.4 完整代码
本案例中,TRViewController.m文件中的完整代码如下所示:
- #import "TRViewController.h"
- @interface TRViewController ()
- @property (weak, nonatomic) IBOutlet UIImageView *imageView;
- @property (strong, nonatomic)UIDynamicAnimator *animator;
- @property (strong, nonatomic)UIGravityBehavior *gravityBehavior;
- @end
- @implementation TRViewController
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- //将self.view设置为动力坐标系的参考
- UIDynamicAnimator *animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
- //给imageView添加重力行为
- UIGravityBehavior *gravity = [[UIGravityBehavior alloc]initWithItems:@[self.imageView]];
- self.gravityBehavior = gravity;
- self.animator = animator;
- }
- - (IBAction)start
- {
- //当点击start按钮时将重力行为添加到self.animator
- [self.animator addBehavior:self.gravityBehavior];
- }
- - (IBAction)end
- {
- //当点击end按钮时将重力行为移除
- [self.animator removeBehavior:self.gravityBehavior];
- }
- @end
2 给视图添加碰撞效果
2.1 问题
如果给某个视图添加了UICollisionBehavior碰撞行为,该视图就有碰撞能力,能与引用视图产生碰撞效果,本案例使用UICollisionBehavior给视图添加碰撞效果,如图-3所示:
图-3
2.2 方案
首先在创建好的Xcode项目的Storyboard中拖放一个ImageView控件,在右边栏的检查器中设置好ImageView的显示图片。将ImageView控件关联成TRCollisionViewController的属性boxImageView。
其次创建一个TRBackgroundView类继承至UIView,该类有一个UIBezierPath类型的公开属性path。在Storyboard中将View关联成TRBackgroundView。
在TRBackgroundView.m文件中重写initWithCoder:方法和drawRect:绘制方法,initWithCoder:方法中使用colorWithPatternImage:方法设置背景颜色,drawRect:方法中则根据path属性进行绘制。
然后在TRCollisionViewController类中定义一个UIDynamicAnimator类型的属性animator。
在viewDidLoad方法中使boxImageView旋转45度,使一个角朝下。
在viewDidAppear:方法中创建属性animator,将self.view设置为属性animator的referenceView引用视图,给属性boxImageView添加重力行为和碰撞行为,使boxImageView和绘制出来的path进行碰撞。
最后通过实现UICollisionBehaviorDelegate的协议方法,当碰撞开始时boxImageView颜色发生改变。
2.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:拖放ImageView控件
首先在创建好的Xcode项目的Storyboard中拖放一个ImageView控件,在右边栏的检查器中设置好ImageView的显示图片。将ImageView控件关联成TRCollisionViewController的属性boxImageView,代码如下所示:
- @interface TRCollisionViewController () <UICollisionBehaviorDelegate>
- @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
- @end
步骤二:创建TRBackgroundView类
首先创建一个TRBackgroundView类继承至UIView,该类有一个UIBezierPath类型的公开属性path。在Storyboard中将View关联成TRBackgroundView,如图-4所示:
图-4
然后在TRBackgroundView.m文件中重写initWithCoder:方法和drawRect:绘制方法,initWithCoder:方法中使用colorWithPatternImage:方法设置背景颜色,drawRect:方法中则根据path属性进行绘制,代码如下所示:
- #import "TRBackgroundView.h"
- @implementation TRBackgroundView
- - (id)initWithCoder:(NSCoder *)aDecoder
- {
- self = [super initWithCoder:aDecoder];
- if(self){
- self.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"BackgroundTile"]];
- }
- return self;
- }
- - (void)drawRect:(CGRect)rect
- {
- [[UIColor redColor]setFill];
- [[UIColor greenColor]setStroke];
- [self.path stroke];
- [self.path fill];
- }
- @end
步骤三:添加重力行为和碰撞行为
首先在TRCollisionViewController类中定义一个UIDynamicAnimator类型的私用属性animator,代码如下所示:
- @interface TRCollisionViewController () <UICollisionBehaviorDelegate>
- @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
- @property (strong, nonatomic) UIDynamicAnimator *animator;
- @end
然后在viewDidLoad方法中使boxImageView旋转45度,使boxImageView的一个角朝下,代码如下所示:
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- self.boxImageView.transform = CGAffineTransformRotate(self.boxImageView.transform, M_PI_4);
- }
最后在viewDidAppear:方法中创建属性animator,将self.view设置为属性animator的referenceView引用视图,给属性boxImageView添加重力行为和碰撞行为,使boxImageView和绘制出来的path进行碰撞,代码如下所示:
- - (void)viewDidAppear:(BOOL)animated
- {
- UIDynamicAnimator *animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
- self.animator = animator;
- //创建重力行为
- UIGravityBehavior *gravity = [[UIGravityBehavior alloc]initWithItems:@[self.boxImageView]];
- //重力的强度
- gravity.magnitude = 0.1;
- //重力的方向
- gravity.gravityDirection = CGVectorMake(0, 1);
- [animator addBehavior:gravity];
- //创建碰撞行为
- UICollisionBehavior *collision = [[UICollisionBehavior alloc]initWithItems:@[self.boxImageView]];
- //将引用视图的四周翻译成可碰撞的四个边
- collision.translatesReferenceBoundsIntoBoundary = YES;
- //设置delegate
- collision.collisionDelegate = self;
- //用UIBezierPath创建一个形状并具有碰撞行为
- UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(20, 450, 280, 30) cornerRadius:10.0];
- TRBackgroundView *myView = (TRBackgroundView *)self.view;
- myView.path = path;
- [myView setNeedsDisplay];
- [collision addBoundaryWithIdentifier:@"MyPathIdenrifier" forPath:path];
- [animator addBehavior:collision];
- }
步骤四:实现协议方法
实现UICollisionBehaviorDelegate的协议方法beganContactForItem: withBoundaryIdentifier:atPoint:,完碰撞开始时boxImageView颜色发生改变,代码如下所示:
- - (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(id <NSCopying>)identifier atPoint:(CGPoint)p
- {
- UIImageView *box = (UIImageView *)item;
- box.tintColor = [UIColor redColor];
- // imageWithRenderingMode:IOS7提供的新的功能,设置一个UIImage在渲染时是否使用当前视图的Tint Color
- //UIImageRenderingModeAlwaysTemplate表示始终根据Tint Color绘制图片,忽略图片的颜色信息。
- box.image = [box.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
- NSLog(@"%@,%@,point:(%.0f, %.0f", item, identifier, p.x, p.y);
- }
2.4 完整代码
本案例中,TRCollisionViewController.m文件中的完整代码如下所示:
- #import "TRCollisionViewController.h"
- #import "TRBackgroundView.h"
- @interface TRCollisionViewController () <UICollisionBehaviorDelegate>
- @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
- @property (strong, nonatomic) UIDynamicAnimator *animator;
- @end
- @implementation TRCollisionViewController
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- self.boxImageView.transform = CGAffineTransformRotate(self.boxImageView.transform, M_PI_4);
- }
- - (void)viewDidAppear:(BOOL)animated
- {
- UIDynamicAnimator *animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
- self.animator = animator;
- //重力行为
- UIGravityBehavior *gravity = [[UIGravityBehavior alloc]initWithItems:@[self.boxImageView]];
- //重力的强度
- gravity.magnitude = 0.1;
- //重力的方向
- gravity.gravityDirection = CGVectorMake(0, 1);
- [animator addBehavior:gravity];
- //碰撞行为
- UICollisionBehavior *collision = [[UICollisionBehavior alloc]initWithItems:@[self.boxImageView]];
- //将引用视图的四周翻译成可碰撞的四个边
- collision.translatesReferenceBoundsIntoBoundary = YES;
- collision.collisionDelegate = self;
- //用UIBezierPath创建一个形状并具有碰撞行为
- UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(20, 450, 280, 30) cornerRadius:10.0];
- TRBackgroundView *myView = (TRBackgroundView *)self.view;
- myView.path = path;
- [myView setNeedsDisplay];
- [collision addBoundaryWithIdentifier:@"MyPathIdenrifier" forPath:path];
- [animator addBehavior:collision];
- }
- - (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(id <NSCopying>)identifier atPoint:(CGPoint)p
- {
- UIImageView *box = (UIImageView *)item;
- box.tintColor = [UIColor redColor];
- // imageWithRenderingMode:IOS7提供的新的功能,设置一个UIImage在渲染时是否使用当前视图的Tint Color
- //UIImageRenderingModeAlwaysTemplate表示始终根据Tint Color绘制图片,忽略图片的颜色信息。
- box.image = [box.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
- NSLog(@"%@,%@,point:(%.0f, %.0f", item, identifier, p.x, p.y);
- }
- - (void)collisionBehavior:(UICollisionBehavior*)behavior endedContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(id <NSCopying>)identifier
- {
- NSLog(@"....");
- }
- @end
本案例中,TRBackgroundView.h文件中的完整代码如下所示:
- #import<UIKit/UIKit.h>
- @interface TRBackgroundView : UIView
- @property (nonatomic, strong) UIBezierPath *path;
- @end
本案例中,TRBackgroundView.m文件中的完整代码如下所示:
- #import "TRBackgroundView.h"
- @implementation TRBackgroundView
- - (id)initWithCoder:(NSCoder *)aDecoder
- {
- self = [super initWithCoder:aDecoder];
- if(self){
- self.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"BackgroundTile"]];
- }
- return self;
- }
- - (void)drawRect:(CGRect)rect
- {
- [[UIColor redColor]setFill];
- [[UIColor greenColor]setStroke];
- [self.path stroke];
- [self.path fill];
- }
- @end
3 给视图添加吸附效果
3.1 问题
UIAttachmentBehavior吸附行为,一个视图产生某种行为后相关联的对象也随之一起运动,本案例使用UIAttachmentBehavior给视图添加吸附效果,是两个视图产生关联行为,如图-5所示:
图-5
3.2 方案
首先在创建好的Xcode项目的Storyboard中拖放一个ImageView控件,在右边栏的检查器中设置好ImageView的显示图片。将ImageView控件关联成TRAttachmentViewController的属性boxImageView。
本案例同样需要一个TRBackgroundView类,直接使用上一个案例的TRBackgroundView类即可,在Storyboard中将View关联成TRBackgroundView。
然后在TRAttachmentViewController类中定义三个属性分别为UIDynamicAnimator类型的animator,UIGravityBehavior类型的gravityBehavior,以及UIAttachmentBehavior类型的attachmentBehavior,并且使用重写setter的方法进行初始化。
在Storyboard中给View添加UIPanGestureRecognizer类型的手势,关联成pan:方法。
最后在TRAttachmentViewController中实现pan:方法,每次触摸屏幕则在boxImageView的中心点和手指触摸点绘制一条直线,并且boxImageView和手指触摸点产生关联的动力行为。
3.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:拖放ImageView控件
首先在创建好的Xcode项目的Storyboard中拖放一个ImageView控件,在右边栏的检查器中设置好ImageView的显示图片。将ImageView控件关联成TRAttachmentViewController的属性boxImageView,代码如下所示:
- @interface TRAttachmentViewController ()
- @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
- @end
本案例同样需要一个TRBackgroundView类,直接使用上一个案例的TRBackgroundView类即可,在Storyboard中将View关联成TRBackgroundView。
步骤二:定义属性
在TRAttachmentViewController类中定义三个属性分别为UIDynamicAnimator类型的animator,UIGravityBehavior类型的gravityBehavior,以及UIAttachmentBehavior类型的attachmentBehavior,并且使用重写setter的方法进行初始化,代码如下所示:
- @interface TRAttachmentViewController ()
- @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
- @property (strong, nonatomic) UIDynamicAnimator *animator;
- @property (strong, nonatomic) UIGravityBehavior *gravityBehavior;
- @property (strong, nonatomic) UIAttachmentBehavior *attachmentBehavior;
- @end
- //重写setter方法
- - (UIDynamicAnimator *)animator
- {
- if(!_animator)_animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
- return _animator;
- }
- - (UIGravityBehavior *)gravityBehavior
- {
- if (!_gravityBehavior) {
- _gravityBehavior = [[UIGravityBehavior alloc]initWithItems:@[self.boxImageView]];
- }
- return _gravityBehavior;
- }
- - (UIAttachmentBehavior *)attachmentBehavior
- {
- CGPoint attachedToAnchor = CGPointMake(self.boxImageView.center.x, self.boxImageView.frame.origin.y - 50);
- if (!_attachmentBehavior) {
- //使用initWithItem:attachedToAnchor:方法进行初始化,使boxImageView和attachedToAnchor产生关联行为
- _attachmentBehavior = [[UIAttachmentBehavior alloc]initWithItem:self.boxImageView attachedToAnchor:attachedToAnchor];
- }
- return _attachmentBehavior;
- }
步骤三:添加手势,实现关联动力行为
首先在Storyboard中给View添加UIPanGestureRecognizer类型的手势,关联成pan:方法。
然后在TRAttachmentViewController中实现pan:方法,该方法中根据手势的状态在boxImageView的中心点和手指触摸点绘制一条直线,并且boxImageView和手指触摸点产生关联的动力行为,代码如下所示:
- - (IBAction)pan:(UIPanGestureRecognizer *)sender
- {
- if (sender.state == UIGestureRecognizerStateBegan) {
- //手势开始添加关联行为和重力行为
- [self.animator addBehavior:self.gravityBehavior];
- self.attachmentBehavior.anchorPoint = [sender locationInView:self.view];
- //阻尼
- self.attachmentBehavior.damping = 0.1;
- //频率
- self.attachmentBehavior.frequency = 1.0;
- [self.animator addBehavior:self.attachmentBehavior];
- [self drawLine];
- [self.view setNeedsDisplay];
- }
- if(sender.state == UIGestureRecognizerStateChanged){
- CGPoint location = [sender locationInView:self.view];
- //手指移动过程中改变anchorPoint
- self.attachmentBehavior.anchorPoint = location;
- [self drawLine];
- [self.view setNeedsDisplay];
- }else if(sender.state == UIGestureRecognizerStateEnded){
- //手势结束移除关联行为和重力行为
- [self.animator removeBehavior:self.attachmentBehavior];
- [self.animator removeBehavior:self.gravityBehavior];
- ((TRBackgroundView *)self.view).path = nil;
- [self.view setNeedsDisplay];
- }
- }
- - (void)drawLine
- {
- __weak UIAttachmentBehavior *weakAttachmentBehavior = self.attachmentBehavior;
- __weak UIImageView *weakBoxImageView = self.boxImageView;
- __weak TRBackgroundView *weakMyView = (TRBackgroundView *)self.view;
- self.attachmentBehavior.action = ^{
- UIBezierPath *path = [UIBezierPath bezierPath];
- [path moveToPoint:weakAttachmentBehavior.anchorPoint];
- [path addLineToPoint:weakBoxImageView.center];
- path.lineWidth = 2;
- weakMyView.path = path;
- [weakMyView setNeedsDisplay];
- };
- }
3.4 完整代码
本案例中,TRAttachmentViewController.m文件中的完整代码如下所示:
- #import "TRAttachmentViewController.h"
- #import "TRBackgroundView.h"
- @interface TRAttachmentViewController ()
- @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
- @property (strong, nonatomic) UIDynamicAnimator *animator;
- @property (strong, nonatomic) UIGravityBehavior *gravityBehavior;
- @property (strong, nonatomic) UIAttachmentBehavior *attachmentBehavior;
- @end
- @implementation TRAttachmentViewController
- - (UIDynamicAnimator *)animator
- {
- if(!_animator)_animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
- return _animator;
- }
- - (UIGravityBehavior *)gravityBehavior
- {
- if (!_gravityBehavior) {
- _gravityBehavior = [[UIGravityBehavior alloc]initWithItems:@[self.boxImageView]];
- }
- return _gravityBehavior;
- }
- - (UIAttachmentBehavior *)attachmentBehavior
- {
- CGPoint attachedToAnchor = CGPointMake(self.boxImageView.center.x, self.boxImageView.frame.origin.y - 50);
- if (!_attachmentBehavior) {
- _attachmentBehavior = [[UIAttachmentBehavior alloc]initWithItem:self.boxImageView attachedToAnchor:attachedToAnchor];
- }
- return _attachmentBehavior;
- }
- - (IBAction)pan:(UIPanGestureRecognizer *)sender
- {
- if (sender.state == UIGestureRecognizerStateBegan) {
- [self.animator addBehavior:self.gravityBehavior];
- self.attachmentBehavior.anchorPoint = [sender locationInView:self.view];
- //阻尼
- self.attachmentBehavior.damping = 0.1;
- //频率
- self.attachmentBehavior.frequency = 1.0;
- [self.animator addBehavior:self.attachmentBehavior];
- [self drawLine];
- [self.view setNeedsDisplay];
- }
- if(sender.state == UIGestureRecognizerStateChanged){
- CGPoint location = [sender locationInView:self.view];
- self.attachmentBehavior.anchorPoint = location;
- [self drawLine];
- [self.view setNeedsDisplay];
- }else if(sender.state == UIGestureRecognizerStateEnded){
- [self.animator removeBehavior:self.attachmentBehavior];
- [self.animator removeBehavior:self.gravityBehavior];
- ((TRBackgroundView *)self.view).path = nil;
- [self.view setNeedsDisplay];
- }
- }
- - (void)drawLine
- {
- __weak UIAttachmentBehavior *weakAttachmentBehavior = self.attachmentBehavior;
- __weak UIImageView *weakBoxImageView = self.boxImageView;
- __weak TRBackgroundView *weakMyView = (TRBackgroundView *)self.view;
- self.attachmentBehavior.action = ^{
- UIBezierPath *path = [UIBezierPath bezierPath];
- [path moveToPoint:weakAttachmentBehavior.anchorPoint];
- [path addLineToPoint:weakBoxImageView.center];
- path.lineWidth = 2;
- weakMyView.path = path;
- [weakMyView setNeedsDisplay];
- };
- }
- @end
4 给视图添加闪的效果
4.1 问题
本案例使用UISnapBehavior给视图添加闪的效果,点击屏幕某处imageView就闪向某处,如图-6所示:
图-6
4.2 方案
首先在创建好的Xcode项目的Storyboard中拖放一个ImageView控件,在右边栏的检查器中设置好ImageView的显示图片。将ImageView控件关联成TRSnapViewController的属性boxImageView。
从对象库中拖放一个TapGestureRecognizer类型的手势到Storyboard的场景中,并将手势关联成TRSnapViewController的动作方法tap:。
其次在TRSnapViewController中定义两个私有属性UIDynamicAnimator类型的animator和UISnapBehavior类型的snapBehavior。重写setter方法对animator属性进行初始化。
最后实现tap:方法,该方法中创建snapBehavior行为,使boxImageView闪向手指触摸的地方。
4.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:搭建Storyboard界面
首先在创建好的Xcode项目的Storyboard中拖放一个ImageView控件,在右边栏的检查器中设置好ImageView的显示图片。将ImageView控件关联成TRSnapViewController的属性boxImageView,代码如下所示:
- @interface TRSnapViewController ()
- @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
- @end
然后从对象库中拖放一个TapGestureRecognizer类型的手势到Storyboard的场景中,并将手势关联成TRSnapViewController的动作方法tap:。
步骤二:定义属性animator和snapBehavior
首先在TRSnapViewController中定义两个私有属性UIDynamicAnimator类型的animator和UISnapBehavior类型的snapBehavior。
然后重写setter方法对animator属性进行初始化,代码如下所示:
- @interface TRSnapViewController ()
- @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
- @property (strong, nonatomic) UIDynamicAnimator *animator;
- @property (strong, nonatomic) UISnapBehavior *snapBehavior;
- @end
最后实现tap:方法,该方法中创建snapBehavior行为,使boxImageView闪向手指触摸的地方,代码如下所示:
- - (IBAction)tap:(UITapGestureRecognizer *)sender
- {
- CGPoint point = [sender locationInView:self.view];
- [self.animator removeBehavior:self.snapBehavior];
- self.snapBehavior = [[UISnapBehavior alloc]initWithItem:self.boxImageView snapToPoint:point];
- [self.animator addBehavior:self.snapBehavior];
- }
4.4 完整代码
本案例中,TRSnapViewController.m文件中的完整代码如下所示:
- #import "TRSnapViewController.h"
- @interface TRSnapViewController ()
- @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
- @property (strong, nonatomic) UIDynamicAnimator *animator;
- @property (strong, nonatomic) UISnapBehavior *snapBehavior;
- @end
- @implementation TRSnapViewController
- - (UIDynamicAnimator *)animator
- {
- if (!_animator) {
- _animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
- }
- return _animator;
- }
- - (IBAction)tap:(UITapGestureRecognizer *)sender
- {
- CGPoint point = [sender locationInView:self.view];
- [self.animator removeBehavior:self.snapBehavior];
- self.snapBehavior = [[UISnapBehavior alloc]initWithItem:self.boxImageView snapToPoint:point];
- [self.animator addBehavior:self.snapBehavior];
- }
- @end
5 给视图添加推(拍)效果和UIDynamicItemBehavior
5.1 问题
本案例使用UIPushBehavior和UIDynamicItemBehavior给视图添加pushBehavior行为和dynamicItemBehavior行为,使视图可以在某个动作结束后,继续沿轨迹运动,如图-7所示:
图-7
5.2 方案
首先在创建好的Xcode项目的Storyboard中拖放一个ImageView控件,在右边栏的检查器中设置好ImageView的显示图片。将ImageView控件关联成TRPushViewController的属性boxImageView。
从对象库中拖放一个PanGestureRecognizer类型的手势到Storyboard的场景中,并将手势关联成TRPushViewController的动作方法panAction:。
其次在TRPushViewController中定义两个私有属性UIDynamicAnimator类型的animator和UIPushBehavior类型pushBehavior。重写setter方法对animator属性进行初始化。
然后创建碰撞行为和push行为,由于碰撞行为需要计算视图边距,需要获取self.topLayoutGuide.length的值,所以将碰撞行为和push行为的代码写在viewDidAppear:方法中。
最后实现tap:方法,该方法中boxImageView随着手指滑动实现碰撞和推动,并具有摩擦和反弹效果。
5.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:搭建Storyboard界面
首先在创建好的Xcode项目的Storyboard中拖放一个ImageView控件,在右边栏的检查器中设置好ImageView的显示图片。将ImageView控件关联成TRPushViewController的属性boxImageView,代码如下所示:
- @interface TRPushViewController ()
- @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
- @end
从对象库中拖放一个PanGestureRecognizer类型的手势到Storyboard的场景中,并将手势关联成TRPushViewController的动作方法panAction:。
步骤二:定义属性animator和pushBehavior
在TRPushViewController中定义两个私有属性UIDynamicAnimator类型的animator和UIPushBehavior类型pushBehavior,代码如下所示:
- @interface TRPushViewController ()
- @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
- @property (strong, nonatomic) UIDynamicAnimator *animator;
- @property (strong, nonatomic)UIPushBehavior *pushBehavior;
- @end
然后重写setter方法对animator属性进行初始化,代码如下所示:
- - (UIDynamicAnimator *)animator
- {
- if(!_animator)_animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
- return _animator;
- }
属性三:创建动力行为,实现手势的动作方法
首先创建碰撞行为和push行为,由于碰撞行为需要计算视图边距,需要获取self.topLayoutGuide.length的值,所以将碰撞行为和push行为的代码写在viewDidAppear:方法中,代码如下所示:
- - (void)viewDidAppear:(BOOL)animated
- {
- [super viewDidAppear:animated];
- //创建碰撞行为
- UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc]initWithItems:@[self.boxImageView]];
- [collisionBehavior setTranslatesReferenceBoundsIntoBoundaryWithInsets:UIEdgeInsetsMake(self.topLayoutGuide.length, 0, self.bottomLayoutGuide.length, 0)];
- [self.animator addBehavior:collisionBehavior];
- //创建pushBehavior行为,UIPushBehaviorModeContinuous模式表示推力持续施加
- self.pushBehavior = [[UIPushBehavior alloc]initWithItems:@[self.boxImageView] mode:UIPushBehaviorModeContinuous];
- [self.animator addBehavior:self.pushBehavior];
- }
然后实现tap:方法,该方法中boxImageView随着手指滑动实现碰撞和推动,代码如下所示:
- - (IBAction)panAction:(UIPanGestureRecognizer *)sender {
- CGPoint point = [sender locationInView:self.view];
- CGPoint center = self.boxImageView.center;
- //计算移动的方向
- CGFloat angle = atan2(center.y - point.y, center.x - point.x) + M_PI;
- self.pushBehavior.angle = angle;
- //计算移动的速度
- CGFloat distance = sqrt(powf(point.x - center.x, 2) + powf(point.y - center.y, 2));
- self.pushBehavior.magnitude = distance / 10;
- //激活
- self.pushBehavior.active = YES;
- }
最后添加dynamicItemBehavior行为,是视图在移动过程中产生摩擦和反弹,代码如下所示:
- - (IBAction)panAction:(UIPanGestureRecognizer *)sender {
- CGPoint point = [sender locationInView:self.view];
- CGPoint center = self.boxImageView.center;
- //计算移动的方向
- CGFloat angle = atan2(center.y - point.y, center.x - point.x) + M_PI;
- self.pushBehavior.angle = angle;
- //计算移动的速度
- CGFloat distance = sqrt(powf(point.x - center.x, 2) + powf(point.y - center.y, 2));
- self.pushBehavior.magnitude = distance / 10;
- //激活
- self.pushBehavior.active = YES;
- //添加UIDynamicItemBehavior
- UIDynamicItemBehavior *itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.boxImageView]];
- //设置摩擦力大小
- itemBehavior.friction = 0.2;
- //允许旋转
- itemBehavior.allowsRotation = YES;
- //设置旋转速度
- [itemBehavior addAngularVelocity:M_PI_4 forItem:self.boxImageView];
- [self.animator addBehavior:itemBehavior];
- }
5.4 完整代码
本案例中,TRPushViewController.m文件中的完整代码如下所示:
- #import "TRPushViewController.h"
- @interface TRPushViewController ()
- @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
- @property (strong, nonatomic) UIDynamicAnimator *animator;
- @property (strong, nonatomic)UIPushBehavior *pushBehavior;
- @end
- @implementation TRPushViewController
- - (UIDynamicAnimator *)animator
- {
- if(!_animator)_animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
- return _animator;
- }
- - (void)viewDidAppear:(BOOL)animated
- {
- [super viewDidAppear:animated];
- //创建碰撞行为
- UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc]initWithItems:@[self.boxImageView]];
- [collisionBehavior setTranslatesReferenceBoundsIntoBoundaryWithInsets:UIEdgeInsetsMake(self.topLayoutGuide.length, 0, self.bottomLayoutGuide.length, 0)];
- [self.animator addBehavior:collisionBehavior];
- //创建pushBehavior行为,UIPushBehaviorModeContinuous模式表示推力持续施加
- self.pushBehavior = [[UIPushBehavior alloc]initWithItems:@[self.boxImageView] mode:UIPushBehaviorModeContinuous];
- [self.animator addBehavior:self.pushBehavior];
- }
- - (IBAction)panAction:(UIPanGestureRecognizer *)sender {
- CGPoint point = [sender locationInView:self.view];
- CGPoint center = self.boxImageView.center;
- //计算移动的方向
- CGFloat angle = atan2(center.y - point.y, center.x - point.x) + M_PI;
- self.pushBehavior.angle = angle;
- //计算移动的速度
- CGFloat distance = sqrt(powf(point.x - center.x, 2) + powf(point.y - center.y, 2));
- self.pushBehavior.magnitude = distance / 10;
- //激活
- self.pushBehavior.active = YES;
- //添加UIDynamicItemBehavior
- UIDynamicItemBehavior *itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.boxImageView]];
- //设置摩擦力大小
- itemBehavior.friction = 0.2;
- //允许旋转
- itemBehavior.allowsRotation = YES;
- //设置旋转速度
- [itemBehavior addAngularVelocity:M_PI_4 forItem:self.boxImageView];
- [self.animator addBehavior:itemBehavior];
- }
- @end
6 使用集合视图的布局实现Cell滚动时候的动画效果
6.1 问题
集合视图的布局是其精髓,相当于集合视图的大脑和中枢,负责设置集合视图的一些属性,包括位置、尺寸、透明度、层级关系、形状等。本案例将使用集合视图的布局实现单元格滚动时的动画效果,如图-8所示:
图-8
6.2 方案
首先创建一个SingleViewApplication项目,在StoryBoard中拖放一个CollectionViewController作为根视图控制器,并于TRViewController进行绑定,TRViewController继承至UICollectionViewController。
其次创建一个TRCell类,继承至UICollectionViewCell,TRCell有一个公开属性UILabel类型的label。然后重写initWithFrame:方法,通过frame参数计算出每个cell中子视图label的frame,并且设置cell的contentView的layer属性。
然后创建一个TRCollectionViewCustomLayout继承至UICollectionViewFlowLayout。重写初始化方法initWithCoder:对布局类进行一些初始化的设置,例如滚动方向、间距、item的大小等。
UICollectionViewLayoutAttributes是一个非常重要的类包含了cell布局信息,包括边框、中心点、大小、形状、透明度、层次关系以及是否隐藏等信息。本案例在TRCollectionViewCustomLayout类中重写layoutAttributesForElementsInRect:方法,获取每一个Cell的布局属性,通过attributes.transform3D实现Cell的缩放动画。
最后在TRViewController的viewDidLoad方法中注册集合视图的cell,TRViewController遵守集合视图协议,并且实现协议方法给集合视图加载数据。
6.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:创建集合视图项目
首先创建一个SingleViewApplication项目,在StoryBoard中拖放一个CollectionViewController作为根视图控制器,并于TRViewController进行绑定,TRViewController继承至UICollectionViewController,Storyboard中的界面如图-9所示:
图-9
步骤二:创建TRCell类
首先创建一个TRCell类,继承至UICollectionViewCell,TRCell有一个公开属性UILabel类型的label,代码如下所示:
- @interface TRCell : UICollectionViewCell
- @property (nonatomic, strong) UILabel *label;
- @end
然后重写initWithFrame:方法,通过frame参数计算出每个cell中子视图label的frame,并且设置cell的contentView的layer属性,代码如下所示:
- - (id)initWithFrame:(CGRect)frame
- {
- self = [super initWithFrame:frame];
- if (self) {
- //创建并设置label属性
- self.label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
- self.label.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
- self.label.textAlignment = NSTextAlignmentCenter;
- self.label.backgroundColor = [UIColor lightGrayColor];
- self.label.textColor = [UIColor blackColor];
- [self.contentView addSubview:self.label];
- //设置contentView的layer属性,添加圆角和边框
- self.contentView.layer.borderWidth = 1.0f;
- self.contentView.layer.cornerRadius = 8.0;
- self.contentView.layer.masksToBounds = YES;
- self.contentView.layer.borderColor = [UIColor whiteColor].CGColor;
- }
- return self;
- }
步骤三:创建布局类TRCollectionViewCustomLayout
首先创建一个TRCollectionViewCustomLayout继承至UICollectionViewFlowLayout。重写初始化方法initWithCoder:对布局类进行一些初始化的设置,例如滚动方向、间距、item的大小等,代码如下所示:
- - (id)initWithCoder:(NSCoder *)aDecoder
- {
- self = [super initWithCoder:aDecoder];
- if (self) {
- [self initiate];
- }
- return self;
- }
- - (void)initiate
- {
- //设置Cell的大小
- self.itemSize = CGSizeMake(100, 100);
- //设置CollectionView的滚动方向
- self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
- //设置CollectionView的内边距
- self.sectionInset = UIEdgeInsetsMake(100, 0, 100, 0);
- //设置Cell之间的间距
- self.minimumLineSpacing = 50;
- }
然后在TRCollectionViewCustomLayout类中重写layoutAttributesForElementsInRect:方法,获取每一个Cell的布局属性,通过attributes.transform3D实现Cell的缩放动画,代码如下所示:
- //当边界改变时重新计算布局
- - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
- {
- return YES;
- }
- //返回所有Cell的布局属性
- - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
- {
- //获取每一个Cell的布局属性
- NSArray *array = [super layoutAttributesForElementsInRect:rect];
- CGRect visibleRect;
- visibleRect.origin = self.collectionView.contentOffset;
- visibleRect.size = self.collectionView.bounds.size;
- for (UICollectionViewLayoutAttributes *attributes in array) {
- CGFloat distance = CGRectGetMidX(visibleRect) - attributes.center.x;
- CGFloat distance2 = distance / 100;
- if(ABS(distance) < 100){
- CGFloat zoom = 1 + 0.3* (1 - ABS(distance2));
- attributes.transform3D = CATransform3DMakeScale(zoom, zoom, 1);
- }
- }
- return array;
- }
步骤四:遵守委托协议,实现协议方法
首先在TRViewController的viewDidLoad方法中注册集合视图的cell,代码如下所示:
- static NSString *cellIdentifier = @"MyCell";
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- [self.collectionView registerClass:[TRCell class] forCellWithReuseIdentifier:cellIdentifier];
- }
然后TRViewController遵守集合视图协议,并且实现协议方法给集合视图加载数据,代码如下所示:
- -(NSInteger)collectionView:(UICollectionView *)collectionView
- numberOfItemsInSection:(NSInteger)section
- {
- return 60;
- }
- -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
- cellForItemAtIndexPath:(NSIndexPath *)indexPath
- {
- TRCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
- cell.label.text = [NSString stringWithFormat:@"%d", indexPath.row];
- return cell;
- }
6.4 完整代码
本案例中,TRViewController.m文件中的完整代码如下所示:
- #import "TRViewController.h"
- #import "TRCell.h"
- @implementation TRViewController
- static NSString *cellIdentifier = @"MyCell";
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- [self.collectionView registerClass:[TRCell class] forCellWithReuseIdentifier:cellIdentifier];
- }
- - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
- {
- return 60;
- }
- - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
- {
- TRCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
- cell.label.text = [NSString stringWithFormat:@"%d", indexPath.row];
- return cell;
- }
- @end
本案例中,TRCell.h文件中的完整代码如下所示:
- #import<UIKit/UIKit.h>
- @interface TRCell : UICollectionViewCell
- @property (nonatomic, strong) UILabel *label;
- @end
隐藏
本案例中,TRCell.m文件中的完整代码如下所示:
- #import "TRCell.h"
- @implementation TRCell
- - (id)initWithFrame:(CGRect)frame
- {
- self = [super initWithFrame:frame];
- if (self) {
- self.label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
- self.label.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
- self.label.textAlignment = NSTextAlignmentCenter;
- self.label.backgroundColor = [UIColor lightGrayColor];
- self.label.textColor = [UIColor blackColor];
- [self.contentView addSubview:self.label];
- self.contentView.layer.borderWidth = 1.0f;
- self.contentView.layer.cornerRadius = 8.0;
- self.contentView.layer.masksToBounds = YES;
- self.contentView.layer.borderColor = [UIColor whiteColor].CGColor;
- }
- return self;
- }
- @end
本案例中,TRCollectionViewCustomLayout.m文件中的完整代码如下所示:
- #import "TRCollectionViewCustomLayout.h"
- @implementation TRCollectionViewCustomLayout
- - (id)initWithCoder:(NSCoder *)aDecoder
- {
- self = [super initWithCoder:aDecoder];
- if (self) {
- [self initiate];
- }
- return self;
- }
- - (void)initiate
- {
- //设置Cell的大小
- self.itemSize = CGSizeMake(100, 100);
- //设置CollectionView的滚动方向
- self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
- //设置CollectionView的内边距
- self.sectionInset = UIEdgeInsetsMake(100, 0, 100, 0);
- //设置Cell之间的间距
- self.minimumLineSpacing = 50;
- }
- //当边界改变时重新计算布局
- - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
- {
- return YES;
- }
- //返回所有Cell的布局属性
- - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
- {
- //获取每一个Cell的布局属性
- NSArray *array = [super layoutAttributesForElementsInRect:rect];
- CGRect visibleRect;
- visibleRect.origin = self.collectionView.contentOffset;
- visibleRect.size = self.collectionView.bounds.size;
- for (UICollectionViewLayoutAttributes *attributes in array) {
- CGFloat distance = CGRectGetMidX(visibleRect) - attributes.center.x;
- CGFloat distance2 = distance / 100;
- if(ABS(distance) < 100){
- CGFloat zoom = 1 + 0.3* (1 - ABS(distance2));
- attributes.transform3D = CATransform3DMakeScale(zoom, zoom, 1);
- }
- }
- return array;
- }
- @end