ios objective-c ios7 uikit uikit-dynamics

ios - Implementar UIKitDynamics para arrastrar la vista fuera de la pantalla



objective-c ios7 (3)

La respuesta de @ Rob es excelente (¡avala!), Pero eliminé los cálculos de velocidad angular manual y dejé que UIDynamics haga el trabajo con un UIPushBehavior . Simplemente configure el desplazamiento objetivo de UIPushBehavior y UIDynamics hará el trabajo de cálculo rotativo por usted.

Comience con la misma configuración de @ 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];

Pero modifique el controlador del reconocedor de gestos para usar un 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]; } }

Estoy tratando de averiguar cómo implementar UIKit Dynamics que son similares a los de la aplicación de Jelly (específicamente deslizar hacia abajo para arrastrar la vista fuera de la pantalla).

Vea la animación: http://vimeo.com/83478484 (@ 1: 17)

Entiendo cómo funciona UIKit Dynamics, pero no tengo una gran experiencia en física y, por lo tanto, ¡tengo problemas para combinar los diferentes comportamientos para obtener el resultado deseado!


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) } }


Este tipo de arrastre se puede lograr con un UIAttachmentBehavior en el que creas el comportamiento de adjunto en UIGestureRecognizerStateBegan , cambias el ancla en UIGestureRecognizerStateChanged . Esto logra el arrastre con rotación mientras el usuario realiza el gesto de panorámica.

Después de UIGestureRecognizerStateEnded , puedes eliminar UIAttachmentBehavior , pero luego aplicar un UIDynamicItemBehavior para que la animación continúe a la perfección con las mismas velocidades lineales y angulares que el usuario la arrastraba cuando la soltaba (no olvides usar un bloque de action para determinar cuándo la vista ya no se cruza con la vista de supervisión, por lo que puede eliminar el comportamiento dinámico y, probablemente, la vista también. O, si su lógica determina que desea volver a la ubicación original, puede usar un UISnapBehavior para hacerlo.

Francamente, sobre la base de este breve clip, es un poco difícil determinar con precisión lo que están haciendo, pero estos son los componentes básicos.

Por ejemplo, supongamos que creas una vista que deseas arrastrar fuera de la pantalla:

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];

A continuación, puede crear un reconocedor de gestos para arrastrarlo fuera de la pantalla:

- (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://.com/a/2051861/1271826 return atan2(view.transform.b, view.transform.a); }

Eso produce (mostrando tanto el comportamiento de ajuste instantáneo si no lo arrastra hacia abajo, como el comportamiento dinámico si lo arrastra hacia abajo con éxito):

Esto es solo un ejemplo de una demostración, pero ilustra el uso de un UIAttachmentBehavior durante el gesto de paneo, el uso de un UISnapBehavior si desea recuperarlo si concluye que desea revertir la animación del gesto, pero el uso de UIDynamicItemBehavior para finalizar la animación de arrastrar hacia abajo, fuera de la pantalla, pero haciendo que la transición de UIAttachmentBehavior a la animación final sea lo más suave posible. También agregué un poco de gravedad al mismo tiempo que el UIDynamicItemBehavior final para que acelere suavemente fuera de la pantalla (para que no tarde mucho).

Personaliza esto como mejor te parezca. En particular, ese controlador de gestos de paneo es lo suficientemente poco manejable como para que pueda contemplar la creación de un reconocedor personalizado para limpiar ese código. Pero espero que esto ilustre los conceptos básicos en el uso de UIKit Dynamics para arrastrar una vista desde la parte inferior de la pantalla.