iOS: alterna entre rechazar y desplazar gestos

Hay un comportamiento en la aplicación Line messenger (la aplicación de postría de facto en Japón) que estoy tratando de emular.

Básicamente, tienen un controller de vista modal con una vista de desplazamiento dentro. Cuando la acción de desplazamiento alcanza la parte superior de su contenido, el controller de vista cambia a la perfección a una animation de despido interactiva. Además, cuando el gesto devuelve la vista a la parte superior de la pantalla, el control vuelve a la vista de desplazamiento.

Aquí hay un gif de cómo se ve.

demo gif

Por mi vida, no puedo entender cómo lo hicieron. He intentado algunos methods diferentes, pero todos han fallado, y me faltan ideas. ¿Alguien me puede señalar en la dirección correcta?

EDIT2

Para aclarar, el comportamiento que quiero emular no es simplemente arrastrar la window hacia abajo. Puedo hacer eso, no hay problema.

Quiero saber cómo el mismo gesto de desplazamiento (sin levantar el dedo) activa la transición de rechazo y luego transfiere el control a la vista de desplazamiento después de que la vista se ha arrastrado a la position original.

Esta es la parte que no puedo entender.

Finalizar EDIT2

EDIT1

Esto es lo que tengo hasta ahora. Pude utilizar los methods de delegado de vista de desplazamiento para agregar un selector de destino que maneja la animation de despido normal, pero todavía no funciona como se esperaba.

Creo un UIViewController con un UIWebView como propiedad. Luego lo puse en un UINavigationController , que se presenta de manera modal.

El controller de navigation usa controlleres de animation / transición para el despido interactivo regular (que se puede hacer gesticulando sobre la barra de navigation).

Desde aquí, todo funciona bien, pero el despido no puede activarse desde la vista de desplazamiento.

NavigationController.h

 @interface NavigationController : UINavigationController <UIViewControllerTransitioningDelegate> @property (nonatomic, strong) UIPanGestureRecognizer *gestureRecog; - (void)handleGesture:(UIPanGestureRecognizer*)gestureRecognizer; @end 

NavigationController.m

 #import "NavigationController.h" #import "AnimationController.h" #import "TransitionController.h" @implementation NavigationController { AnimationController *_animator; TransitionController *_interactor; } - (instancetype)init { self = [super init]; self.transitioningDelegate = self; _animator = [[AnimationController alloc] init]; _interactor = [[TransitionController alloc] init]; return self; } - (void)viewDidLoad { [super viewDidLoad]; // Set the gesture recognizer self.gestureRecog = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)]; [self.view addGestureRecognizer:_gestureRecog]; } - (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator { if (animator == _animator && _interactor.hasStarted) { return _interactor; } return nil; } - (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed { if (dismissed == self || [self.viewControllers indexOfObject:dismissed] != NSNotFound) { return _animator; } return nil; } - (void)handleGesture:(UIPanGestureRecognizer *)gestureRecog { CGFloat threshold = 0.3f; CGPoint translation = [gestureRecog translationInView:self.view]; CGFloat verticalMovement = translation.y / self.view.bounds.size.height; CGFloat downwardMovement = fmaxf(verticalMovement, 0.0f); CGFloat downwardMovementPercent = fminf(downwardMovement, 1.0f); switch (gestureRecog.state) { case UIGestureRecognizerStateBegan: { _interactor.hasStarted = YES; [self dismissViewControllerAnimated:YES completion:nil]; break; } case UIGestureRecognizerStateChanged: { if (!_interactor.hasStarted) { _interactor.hasStarted = YES; [self dismissViewControllerAnimated:YES completion:nil]; } _interactor.shouldFinish = downwardMovementPercent > threshold; [_interactor updateInteractiveTransition:downwardMovementPercent]; break; } case UIGestureRecognizerStateCancelled: { _interactor.hasStarted = NO; [_interactor cancelInteractiveTransition]; break; } case UIGestureRecognizerStateEnded: { _interactor.hasStarted = NO; if (_interactor.shouldFinish) { [_interactor finishInteractiveTransition]; } else { [_interactor cancelInteractiveTransition]; } break; } default: { break; } } } @end 

Ahora, tengo que lograr que el gesto se active cuando la vista de desplazamiento ha llegado a la parte superior. Entonces, esto es lo que hice en el controller de vista.

WebViewController.m

 #import "WebViewController.h" #import "NavigationController.h" @interface WebViewController () @property (weak, nonatomic) IBOutlet UIWebView *webView; @end @implementation WebViewController { BOOL _isHandlingPan; CGPoint _topContentOffset; } - (void)viewDidLoad { [super viewDidLoad]; [self.webView.scrollView setDelegate:self]; } - (void)scrollViewDidScroll:(UIScrollView *)scrollView { if ((scrollView.panGestureRecognizer.state == UIGestureRecognizerStateBegan || scrollView.panGestureRecognizer.state == UIGestureRecognizerStateChanged) && ! _isHandlingPan && scrollView.contentOffset.y < self.navigationController.navigationBar.translucent ? -64.0f : 0) { NSLog(@"Adding scroll target"); _topContentOffset = CGPointMake(scrollView.contentOffset.x, self.navigationController.navigationBar.translucent ? -64.0f : 0); _isHandlingPan = YES; [scrollView.panGestureRecognizer addTarget:self action:@selector(handleGesture:)]; } } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { NSLog(@"Did End Dragging"); if (_isHandlingPan) { NSLog(@"Removing action"); _isHandlingPan = NO; [scrollView.panGestureRecognizer removeTarget:self action:@selector(handleGesture:)]; } } - (void)handleGesture:(UIPanGestureRecognizer*)gestureRecognizer { [(NavigationController*)self.navigationController handleGesture:gestureRecognizer]; } 

Esto todavía no funciona del todo bien. Incluso durante la animation de rechazo, la vista de desplazamiento todavía se desplaza con el gesto.

Finalizar EDIT1

Esa es una transición interactiva personalizada.

Primero, necesita configurar transitioningDelegate de UIViewController

 id<UIViewControllerTransitioningDelegate> transitioningDelegate; 

Luego implementa estos dos methods para

  //Asks your delegate for the transition animator object to use when dismissing a view controller. - animationControllerForDismissedController: //Asks your delegate for the interactive animator object to use when dismissing a view controller. - interactionControllerForDismissal: 

Cuando arrastre hacia la parte superior, inicie la transición, puede usar UIPercentDrivenInteractiveTransition para controlar el progreso durante el desplazamiento.

También puede consultar el código fuente de ZFDragableModalTransition

Imagen de ZFDragableModalTransition