实现UIKitDynamics拖动视图closures屏幕

我试图找出类似于Jelly的应用程序(实际上向下拖动查看屏幕)的工具UIKitdynamic。

看animation: http : //vimeo.com/83478484 (@ 1:17 )

我了解UIKit Dynamics如何工作,但没有很好的物理背景,因此无法梳理不同的行为以获得期望的结果!

这种拖动可以通过UIAttachmentBehavior完成,在UIAttachmentBehavior中创buildUIGestureRecognizerStateBegan的附件行为,更改UIGestureRecognizerStateBegan的锚点。 这实现了在用户进行平移手势时随着旋转拖动。

UIGestureRecognizerStateEnded您可以删除UIAttachmentBehavior ,但是随后应用UIDynamicItemBehavior使animation以相同的线性和angular速度无缝地继续,用户在放开时可以拖动它(不要忘记使用action块来确定何时该视图不再与superview相交,因此您可以删除dynamic行为,也可能删除视图)。 或者,如果您的逻辑确定要将其返回到原始位置,则可以使用UISnapBehavior来执行此操作。

坦率地说,在这个短片的基础上,确切地确定他们在做什么有点困难,但这些是基本的构build块。


例如,让我们假设你创build一个你想拖出屏幕的视图:

 UIView *viewToDrag = [[UIView alloc] initWithFrame:...]; viewToDrag.backgroundColor = [UIColor lightGrayColor]; [self.view addSubview:viewToDrag]; UIGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)]; [viewToDrag addGestureRecognizer:pan]; self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view]; 

然后,您可以创build一个手势识别器将其拖出屏幕:

 - (void)handlePan:(UIPanGestureRecognizer *)gesture { static UIAttachmentBehavior *attachment; static CGPoint startCenter; // variables for calculating angular velocity static CFAbsoluteTime lastTime; static CGFloat lastAngle; static CGFloat angularVelocity; if (gesture.state == UIGestureRecognizerStateBegan) { [self.animator removeAllBehaviors]; startCenter = gesture.view.center; // calculate the center offset and anchor point CGPoint pointWithinAnimatedView = [gesture locationInView:gesture.view]; UIOffset offset = UIOffsetMake(pointWithinAnimatedView.x - gesture.view.bounds.size.width / 2.0, pointWithinAnimatedView.y - gesture.view.bounds.size.height / 2.0); CGPoint anchor = [gesture locationInView:gesture.view.superview]; // create attachment behavior attachment = [[UIAttachmentBehavior alloc] initWithItem:gesture.view offsetFromCenter:offset attachedToAnchor:anchor]; // code to calculate angular velocity (seems curious that I have to calculate this myself, but I can if I have to) lastTime = CFAbsoluteTimeGetCurrent(); lastAngle = [self angleOfView:gesture.view]; typeof(self) __weak weakSelf = self; attachment.action = ^{ CFAbsoluteTime time = CFAbsoluteTimeGetCurrent(); CGFloat angle = [weakSelf angleOfView:gesture.view]; if (time > lastTime) { angularVelocity = (angle - lastAngle) / (time - lastTime); lastTime = time; lastAngle = angle; } }; // add attachment behavior [self.animator addBehavior:attachment]; } else if (gesture.state == UIGestureRecognizerStateChanged) { // as user makes gesture, update attachment behavior's anchor point, achieving drag 'n' rotate CGPoint anchor = [gesture locationInView:gesture.view.superview]; attachment.anchorPoint = anchor; } else if (gesture.state == UIGestureRecognizerStateEnded) { [self.animator removeAllBehaviors]; CGPoint velocity = [gesture velocityInView:gesture.view.superview]; // if we aren't dragging it down, just snap it back and quit if (fabs(atan2(velocity.y, velocity.x) - M_PI_2) > M_PI_4) { UISnapBehavior *snap = [[UISnapBehavior alloc] initWithItem:gesture.view snapToPoint:startCenter]; [self.animator addBehavior:snap]; return; } // otherwise, create UIDynamicItemBehavior that carries on animation from where the gesture left off (notably linear and angular velocity) UIDynamicItemBehavior *dynamic = [[UIDynamicItemBehavior alloc] initWithItems:@[gesture.view]]; [dynamic addLinearVelocity:velocity forItem:gesture.view]; [dynamic addAngularVelocity:angularVelocity forItem:gesture.view]; [dynamic setAngularResistance:1.25]; // when the view no longer intersects with its superview, go ahead and remove it typeof(self) __weak weakSelf = self; dynamic.action = ^{ if (!CGRectIntersectsRect(gesture.view.superview.bounds, gesture.view.frame)) { [weakSelf.animator removeAllBehaviors]; [gesture.view removeFromSuperview]; [[[UIAlertView alloc] initWithTitle:nil message:@"View is gone!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show]; } }; [self.animator addBehavior:dynamic]; // add a little gravity so it accelerates off the screen (in case user gesture was slow) UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[gesture.view]]; gravity.magnitude = 0.7; [self.animator addBehavior:gravity]; } } - (CGFloat)angleOfView:(UIView *)view { // http://stackoverflow.com/a/2051861/1271826 return atan2(view.transform.b, view.transform.a); } 

这会产生(如果不拖拽,则显示捕捉行为,如果成功拖拽,则显示dynamic行为):

UIDynamics演示

这只是一个演示的shell,但是它说明了在平移手势中使用UISnapBehavior如果您想要反转手势的animation,但使用UIDynamicItemBehavior完成拖动animation,则使用UIDynamicItemBehavior它从屏幕上UIAttachmentBehavior来,但是从UIAttachmentBehavior到最终animation的过渡尽可能平滑。 我还在最后一个UIDynamicItemBehavior同时添加了一点引力,以便平滑地加速屏幕(所以不会太长)。

自定义这个你认为合适的。 值得注意的是,那个泛手势处理程序是不够的,我可能会考虑创build一个自定义识别器来清理代码。 但是,希望这可以说明使用UIKit Dynamics从屏幕底部拖动​​视图的基本概念。

@罗布的答案是伟大的(upvote it!),但我会删除手动angular速度计算,让UIDynamics与UIPushBehavior工作。 只需设置UIPushBehavior的目标偏移量,UIDynamics将为您执行旋转计算工作。

从@ Rob的相同设置开始:

 UIView *viewToDrag = [[UIView alloc] initWithFrame:...]; viewToDrag.backgroundColor = [UIColor lightGrayColor]; [self.view addSubview:viewToDrag]; UIGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)]; [viewToDrag addGestureRecognizer:pan]; self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view]; 

但是调整手势识别器处理程序以使用UIPushBehavior

 - (void)handlePan:(UIPanGestureRecognizer *)gesture { static UIAttachmentBehavior *attachment; static CGPoint startCenter; if (gesture.state == UIGestureRecognizerStateBegan) { [self.animator removeAllBehaviors]; startCenter = gesture.view.center; // calculate the center offset and anchor point CGPoint pointWithinAnimatedView = [gesture locationInView:gesture.view]; UIOffset offset = UIOffsetMake(pointWithinAnimatedView.x - gesture.view.bounds.size.width / 2.0, pointWithinAnimatedView.y - gesture.view.bounds.size.height / 2.0); CGPoint anchor = [gesture locationInView:gesture.view.superview]; // create attachment behavior attachment = [[UIAttachmentBehavior alloc] initWithItem:gesture.view offsetFromCenter:offset attachedToAnchor:anchor]; // add attachment behavior [self.animator addBehavior:attachment]; } else if (gesture.state == UIGestureRecognizerStateChanged) { // as user makes gesture, update attachment behavior's anchor point, achieving drag 'n' rotate CGPoint anchor = [gesture locationInView:gesture.view.superview]; attachment.anchorPoint = anchor; } else if (gesture.state == UIGestureRecognizerStateEnded) { [self.animator removeAllBehaviors]; CGPoint velocity = [gesture velocityInView:gesture.view.superview]; // if we aren't dragging it down, just snap it back and quit if (fabs(atan2(velocity.y, velocity.x) - M_PI_2) > M_PI_4) { UISnapBehavior *snap = [[UISnapBehavior alloc] initWithItem:gesture.view snapToPoint:startCenter]; [self.animator addBehavior:snap]; return; } // otherwise, create UIPushBehavior that carries on animation from where the gesture left off CGFloat velocityMagnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y)); UIPushBehavior *pushBehavior = [[UIPushBehavior alloc] initWithItems:@[gesture.view] mode:UIPushBehaviorModeInstantaneous]; pushBehavior.pushDirection = CGVectorMake((velocity.x / 10) , (velocity.y / 10)); // some constant to limit the speed of the animation pushBehavior.magnitude = velocityMagnitude / 35.0; CGPoint finalPoint = [gesture locationInView:gesture.view.superview]; CGPoint center = gesture.view.center; [pushBehavior setTargetOffsetFromCenter:UIOffsetMake(finalPoint.x - center.x, finalPoint.y - center.y) forItem:gesture.view]; // when the view no longer intersects with its superview, go ahead and remove it typeof(self) __weak weakSelf = self; pushBehavior.action = ^{ if (!CGRectIntersectsRect(gesture.view.superview.bounds, gesture.view.frame)) { [weakSelf.animator removeAllBehaviors]; [gesture.view removeFromSuperview]; [[[UIAlertView alloc] initWithTitle:nil message:@"View is gone!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show]; } }; [self.animator addBehavior:pushBehavior]; // add a little gravity so it accelerates off the screen (in case user gesture was slow) UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[gesture.view]]; gravity.magnitude = 0.7; [self.animator addBehavior:gravity]; } } 

SWIFT 3.0:

 import UIKit class SwipeToDisMissView: UIView { var animator : UIDynamicAnimator? func initSwipeToDismissView(_ parentView:UIView) { let panGesture = UIPanGestureRecognizer(target: self, action: #selector(SwipeToDisMissView.panGesture)) self.addGestureRecognizer(panGesture) animator = UIDynamicAnimator(referenceView: parentView) } func panGesture(_ gesture:UIPanGestureRecognizer) { var attachment : UIAttachmentBehavior? var lastTime = CFAbsoluteTime() var lastAngle: CGFloat = 0.0 var angularVelocity: CGFloat = 0.0 if gesture.state == .began { self.animator?.removeAllBehaviors() if let gestureView = gesture.view { let pointWithinAnimatedView = gesture.location(in: gestureView) let offset = UIOffsetMake(pointWithinAnimatedView.x - gestureView.bounds.size.width / 2.0, pointWithinAnimatedView.y - gestureView.bounds.size.height / 2.0) let anchor = gesture.location(in: gestureView.superview!) // create attachment behavior attachment = UIAttachmentBehavior(item: gestureView, offsetFromCenter: offset, attachedToAnchor: anchor) // code to calculate angular velocity (seems curious that I have to calculate this myself, but I can if I have to) lastTime = CFAbsoluteTimeGetCurrent() lastAngle = self.angleOf(gestureView) weak var weakSelf = self attachment?.action = {() -> Void in let time = CFAbsoluteTimeGetCurrent() let angle: CGFloat = weakSelf!.angleOf(gestureView) if time > lastTime { angularVelocity = (angle - lastAngle) / CGFloat(time - lastTime) lastTime = time lastAngle = angle } } self.animator?.addBehavior(attachment!) } } else if gesture.state == .changed { if let gestureView = gesture.view { if let superView = gestureView.superview { let anchor = gesture.location(in: superView) if let attachment = attachment { attachment.anchorPoint = anchor } } } } else if gesture.state == .ended { if let gestureView = gesture.view { let anchor = gesture.location(in: gestureView.superview!) attachment?.anchorPoint = anchor self.animator?.removeAllBehaviors() let velocity = gesture.velocity(in: gestureView.superview!) let dynamic = UIDynamicItemBehavior(items: [gestureView]) dynamic.addLinearVelocity(velocity, for: gestureView) dynamic.addAngularVelocity(angularVelocity, for: gestureView) dynamic.angularResistance = 1.25 // when the view no longer intersects with its superview, go ahead and remove it weak var weakSelf = self dynamic.action = {() -> Void in if !gestureView.superview!.bounds.intersects(gestureView.frame) { weakSelf?.animator?.removeAllBehaviors() gesture.view?.removeFromSuperview() } } self.animator?.addBehavior(dynamic) let gravity = UIGravityBehavior(items: [gestureView]) gravity.magnitude = 0.7 self.animator?.addBehavior(gravity) } } } func angleOf(_ view: UIView) -> CGFloat { return atan2(view.transform.b, view.transform.a) } }