IOS - Switch between firing and scrolling modes

The Line messenger app (de facto messenger app in Japan) has behaviors that I'm trying to emulate.

Basically, they have a modal view controller with a scroll list inside. When the scroll action reaches the top of its contents, the view controller smoothly switches to an interactive firing animation. In addition, when the gesture returns the view to the top of the screen, control returns to the scroll view.

A gif of what it looks like is displayed here.

demo gif

In life, I cannot understand how they did it. I tried several different methods, but they all failed, and I have no ideas. Can someone point me in the right direction?

EDIT2

, , , - . , .

, ( ) , , .

, .

End EDIT2

EDIT1

. , , .

UIViewController UIWebView . UINavigationController, .

/ ( ).

, .

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

, , , . , .

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

. .

End EDIT1

+4
1

.

transitioningDelegate UIViewController

id<UIViewControllerTransitioningDelegate> transitioningDelegate;

 //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:

, UIPercentDrivenInteractiveTransition .

ZFDragableModalTransition

ZFDragableModalTransition

ss.gif

+2

Source: https://habr.com/ru/post/1673180/


All Articles