【问题标题】:iOS - Switch between dismiss and scroll gesturesiOS - 在关闭和滚动手势之间切换
【发布时间】:2017-08-18 06:29:11
【问题描述】:

Line messenger 应用程序(日本事实上的 Messenger 应用程序)中存在我试图模仿的行为。

基本上,它们有一个模态视图控制器,里面有一个滚动视图。当滚动动作到达其内容的顶部时,视图控制器会无缝切换到交互式解除动画。此外,当手势将视图返回到屏幕顶部时,控制权将返回到滚动视图。

这是它的外观的 gif。

在我的一生中,我无法弄清楚他们是如何做到的。我尝试了几种不同的方法,但都失败了,而且我没有想法。谁能指出我正确的方向?

EDIT2

为了澄清,我想模仿的行为不仅仅是简单地向下拖动窗口。我能做到,没问题。

我想知道相同的滚动手势(不抬起手指)如何触发解除转换,然后在视图被拖回原始位置后将控制权转移回滚动视图。

这是我想不通的部分。

结束 EDIT2

EDIT1

这是我目前所拥有的。我能够使用滚动视图委托方法来添加一个处理常规解除动画的目标选择器,但它仍然无法按预期工作。

我创建了一个 UIViewController 并带有 UIWebView 作为属性。然后我把它放在UINavigationController 中,以模态形式呈现。

导航控制器使用动画/过渡控制器进行常规交互式解除(可以通过在导航栏上打手势来完成)。

从这里开始,一切正常,但无法从滚动视图触发解雇。

导航控制器.h

@interface NavigationController : UINavigationController <UIViewControllerTransitioningDelegate>

@property (nonatomic, strong) UIPanGestureRecognizer *gestureRecog;

- (void)handleGesture:(UIPanGestureRecognizer*)gestureRecognizer;

@end

导航控制器.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];
}

这仍然不能正常工作。即使在解除动画期间,滚动视图仍会随着手势滚动。

结束 EDIT1

【问题讨论】:

  • 您需要在达到滚动偏移值时添加transitionViewController动画。查看视图控制器转换的示例。
  • 嗨,我猜你现在成功了。您选择什么方法来获得正确的行为?谢谢:)
  • @AnthoPak 不幸的是,我从来没有让这个工作。不过,我确信这是对委托方法的一些巧妙使用。
  • @ABeard89 我发现了这个stackoverflow.com/a/50060144/4894980 我认为它可以适用于全屏模式视图控制器。可能是一个好的开始! :)
  • @ABeard89 你确定吗?我已经尝试过演示,它似乎能够在继续滚动滚动视图时处理取消解雇。

标签: ios uiscrollview uigesturerecognizer modalviewcontroller


【解决方案1】:

正如here 解释的那样,解决方案非常复杂。回答的人 @trungduc 编写了一个在 github 上发布的小演示,以执行所寻求的行为。你可以找到它here

完成这项工作的最简单方法是将附加的 github 存储库中 /TestPanel/Presentation/ 中的 4 个文件复制到您的项目中。然后将PanelAnimationControllerDelegate 添加到包含滚动视图的视图控制器中(即使用协议)。

将以下内容添加到您的视图控制器,以满足协议:

func shouldHandlePanelInteractionGesture() -> Bool {
    return (scrollView.contentOffset.y == 0);
}

添加此项以停用顶部的弹跳效果:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    scrollView.bounces = (scrollView.contentOffset.y > 10);
}

设置scrollView.delegate = self

在展示包含滚动视图的视图控制器之前,请为视图控制器设置以下属性:

ScrollViewController.transitioningDelegate = self.panelTransitioningDelegate
ScrollViewController.modalPresentationStyle = .custom

如果您想更改ScrollViewController 的大小,您需要在PanelPresentationController 文件(4 个之一)中注释掉frameOfPresentedViewInContainerView 的覆盖。然后在presentationTransitionWillBegin 方法中,您需要将let frameOfPresentedViewInContainerView = self.frameOfPresentedViewInContainerView.insetBy(dx: 0, dy: 20) 设置为所需的dx 和dy 插图。

感谢 trungduc 提供的这个惊人的解决方案!

【讨论】:

    【解决方案2】:

    这是一个自定义的交互式过渡。

    首先,您需要设置transitioningDelegate of 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 的图像

    【讨论】:

    • 我用我最成功的代码编辑了我的帖子。我想我在正确的轨道上?还是我的方法有缺陷?
    • 我想我只是看看那个源代码,看看我缺少的部分是什么。
    • CocoaPod 没有做我想做的一件事。这就是我目前陷入困境的地方。我想要相同的滚动手势(不抬起手指)来触发解除转换,并且我希望滚动视图在视图被拖回原始位置后重新获得控制。这是我想不通的部分。
    猜你喜欢
    • 1970-01-01
    • 2011-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多