Навигация поп-вид при прокрутке вправо, например, приложение Instagram iPhone. Как я это понимаю?

Я хочу вывести представление, когда проецируется прямо на экран или работает как кнопка "Назад" на панели навигации.

Я использую:

self.navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;

Эта единственная строка кода для просмотра поп-навигации, и это работает для меня, но когда я прокручиваю форму середины экрана, это не будет работать, как приложение для iPhone Instagram.

Здесь я даю один экран приложения Instagram, в котором вы можете увидеть пример прокрутки правой поп-навигации:

enter image description here

Ответы

Ответ 1

Автоматическая реализация Apple "салфетки справа на поп-VC" работает только в левых 20 точках экрана. Таким образом, они гарантируют, что они не будут взаимодействовать с вашими приложениями. Представьте, что у вас есть UIScrollView на экране, и вы не можете прокручивать вправо, потому что он продолжает выходить из VC. Это было бы неплохо.

Apple говорит здесь:

interactivePopGestureRecognizer

Устройство распознавания жестов, ответственное за высказывание контроллера верхнего уровня из стека навигации. (Только для чтения)

@property (неатомный, только для чтения) UIGestureRecognizer * interactivePopGestureRecognizer

Контроллер навигации устанавливает этот распознаватель жестов в своем представлении и использует его, чтобы вытащить верхний контроллер просмотра с навигации стек. Вы можете использовать это свойство для извлечения распознавателя жестов и привяжите его к поведению других распознавателей жестов в вашем пользователе интерфейс. При связывании распознавателей жеста убедитесь, что они одновременно распознают свои жесты, чтобы распознаватели жестов получают возможность обработать событие.

Итак, вам придется реализовать свой собственный UIGestureRecognizer и привязать его поведение к interactivePopGestureRecognizer вашего UIViewController.


Изменить:

Вот решение, которое я построил. Вы можете реализовать свой собственный переход, соответствующий делегату UIViewControllerAnimatedTransitioning. Это решение работает, но не было тщательно протестировано.

Вы получите скользящий переход интерактивный, чтобы вывести ваши ViewControllers. Вы можете скользить вправо из любого места в представлении.

Известная проблема: если вы запустите панорамирование и остановитесь до половины ширины представления, переход отменяется (ожидаемое поведение). Во время этого процесса представления reset к их исходным кадрам. Это визуальный глюк во время этой анимации.

Классы примера следующие:

UINavigationController > ViewController > SecondViewController

CustomPopTransition.h:

#import <Foundation/Foundation.h>

@interface CustomPopTransition : NSObject <UIViewControllerAnimatedTransitioning>

@end

CustomPopTransition.m:

#import "CustomPopTransition.h"
#import "SecondViewController.h"
#import "ViewController.h"

@implementation CustomPopTransition

- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
    return 0.3;
}

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {

    SecondViewController *fromViewController = (SecondViewController*)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    ViewController *toViewController = (ViewController*)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

    UIView *containerView = [transitionContext containerView];
    [containerView addSubview:toViewController.view];
    [containerView bringSubviewToFront:fromViewController.view];

    // Setup the initial view states
    toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];

    [UIView animateWithDuration:0.3 animations:^{

        fromViewController.view.frame = CGRectMake(toViewController.view.frame.size.width, fromViewController.view.frame.origin.y, fromViewController.view.frame.size.width, fromViewController.view.frame.size.height);

    } completion:^(BOOL finished) {

        // Declare that we've finished
        [transitionContext completeTransition:!transitionContext.transitionWasCancelled];
    }];

}

@end

SecondViewController.h:

#import <UIKit/UIKit.h>

@interface SecondViewController : UIViewController <UINavigationControllerDelegate>

@end

SecondViewController.m:

#import "SecondViewController.h"
#import "ViewController.h"
#import "CustomPopTransition.h"

@interface SecondViewController ()

@property (nonatomic, strong) UIPercentDrivenInteractiveTransition *interactivePopTransition;

@end

@implementation SecondViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.navigationController.delegate = self;

    UIPanGestureRecognizer *popRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePopRecognizer:)];
    [self.view addGestureRecognizer:popRecognizer];
}

-(void)viewDidDisappear:(BOOL)animated {

    [super viewDidDisappear:animated];

    // Stop being the navigation controller delegate
    if (self.navigationController.delegate == self) {
        self.navigationController.delegate = nil;
    }
}

- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {

    // Check if we're transitioning from this view controller to a DSLSecondViewController
    if (fromVC == self && [toVC isKindOfClass:[ViewController class]]) {
        return [[CustomPopTransition alloc] init];
    }
    else {
        return nil;
    }
}

- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController {

    // Check if this is for our custom transition
    if ([animationController isKindOfClass:[CustomPopTransition class]]) {
        return self.interactivePopTransition;
    }
    else {
        return nil;
    }
}

- (void)handlePopRecognizer:(UIPanGestureRecognizer*)recognizer {

    // Calculate how far the user has dragged across the view
    CGFloat progress = [recognizer translationInView:self.view].x / (self.view.bounds.size.width * 1.0);
    progress = MIN(1.0, MAX(0.0, progress));

    if (recognizer.state == UIGestureRecognizerStateBegan) {
        NSLog(@"began");
        // Create a interactive transition and pop the view controller
        self.interactivePopTransition = [[UIPercentDrivenInteractiveTransition alloc] init];
        [self.navigationController popViewControllerAnimated:YES];
    }
    else if (recognizer.state == UIGestureRecognizerStateChanged) {
        NSLog(@"changed");
        // Update the interactive transition progress
        [self.interactivePopTransition updateInteractiveTransition:progress];
    }
    else if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled) {
        NSLog(@"ended/cancelled");
        // Finish or cancel the interactive transition
        if (progress > 0.5) {
            [self.interactivePopTransition finishInteractiveTransition];
        }
        else {
            [self.interactivePopTransition cancelInteractiveTransition];
        }

        self.interactivePopTransition = nil;
    }
}

@end

Ответ 2

Вот Swift-версия ответа Spynet с несколькими модификациями. Во-первых, я определил линейную кривую для анимации UIView. Во-вторых, я добавил полупрозрачный черный фон к представлению внизу для лучшего эффекта. В-третьих, я подклассифицировал UINavigationController. Это позволяет применять переход к любому переходу "Pop" в UINavigationController. Вот код:

CustomPopTransition.swift

import UIKit

class CustomPopTransition: NSObject, UIViewControllerAnimatedTransitioning {

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.3
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
            let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
            else {
                return
        }

        let containerView = transitionContext.containerView
        containerView.insertSubview(toViewController.view, belowSubview: fromViewController.view)

        // Setup the initial view states
        toViewController.view.frame = CGRect(x: -100, y: toViewController.view.frame.origin.y, width: fromViewController.view.frame.size.width, height: fromViewController.view.frame.size.height)

        let dimmingView = UIView(frame: CGRect(x: 0,y: 0, width: toViewController.view.frame.width, height: toViewController.view.frame.height))
        dimmingView.backgroundColor = UIColor.black
        dimmingView.alpha = 0.5

        toViewController.view.addSubview(dimmingView)

        UIView.animate(withDuration: transitionDuration(using: transitionContext),
                       delay: 0,
                       options: UIView.AnimationOptions.curveLinear,
                       animations: {
                        dimmingView.alpha = 0
                        toViewController.view.frame = transitionContext.finalFrame(for: toViewController)
                        fromViewController.view.frame = CGRect(x: toViewController.view.frame.size.width, y: fromViewController.view.frame.origin.y, width: fromViewController.view.frame.size.width, height: fromViewController.view.frame.size.height)
        },
                       completion: { finished in
                        dimmingView.removeFromSuperview()
                        transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
        )
    }
}

PoppingNavigationController.swift

import UIKit

class PoppingNavigationController : UINavigationController, UINavigationControllerDelegate {
    var interactivePopTransition: UIPercentDrivenInteractiveTransition!

    override func viewDidLoad() {
        self.delegate = self
    }

    func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
        addPanGesture(viewController: viewController)
    }

    func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        if (operation == .pop) {
            return CustomPopTransition()
        }
        else {
            return nil
        }
    }

    func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        if animationController.isKind(of: CustomPopTransition.self) {
            return interactivePopTransition
        }
        else {
            return nil
        }
    }

    func addPanGesture(viewController: UIViewController) {
        let popRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanRecognizer(recognizer:)))
        viewController.view.addGestureRecognizer(popRecognizer)
    }

    @objc
    func handlePanRecognizer(recognizer: UIPanGestureRecognizer) {
        // Calculate how far the user has dragged across the view
        var progress = recognizer.translation(in: self.view).x / self.view.bounds.size.width
        progress = min(1, max(0, progress))
        if (recognizer.state == .began) {
            // Create a interactive transition and pop the view controller
            self.interactivePopTransition = UIPercentDrivenInteractiveTransition()
            self.popViewController(animated: true)
        }
        else if (recognizer.state == .changed) {
            // Update the interactive transition progress
            interactivePopTransition.update(progress)
        }
        else if (recognizer.state == .ended || recognizer.state == .cancelled) {
            // Finish or cancel the interactive transition
            if (progress > 0.5) {
                interactivePopTransition.finish()
            }
            else {
                interactivePopTransition.cancel()
            }
            interactivePopTransition = nil
        }
    }
}

Пример результата: enter image description here

Ответ 3

На самом деле нет необходимости создавать собственное решение для этого, подкласс UINavigationController и ссылки на встроенный жест работают просто отлично, как описано здесь.

То же решение в Swift:

public final class MyNavigationController: UINavigationController {

  public override func viewDidLoad() {
    super.viewDidLoad()


    self.view.addGestureRecognizer(self.fullScreenPanGestureRecognizer)
  }

  private lazy var fullScreenPanGestureRecognizer: UIPanGestureRecognizer = {
    let gestureRecognizer = UIPanGestureRecognizer()

    if let cachedInteractionController = self.value(forKey: "_cachedInteractionController") as? NSObject {
      let string = "handleNavigationTransition:"
      let selector = Selector(string)
      if cachedInteractionController.responds(to: selector) {
        gestureRecognizer.addTarget(cachedInteractionController, action: selector)
      }
    }

    return gestureRecognizer
  }()
}

Если вы сделаете это, также реализуйте следующую функцию UINavigationControllerDelegate чтобы избежать странного поведения в контроллере корневого представления:

public func navigationController(_: UINavigationController,
                                 didShow _: UIViewController, animated _: Bool) {
  self.fullScreenPanGestureRecognizer.isEnabled = self.viewControllers.count > 1
}

Ответ 4

Подклассификация UINavigationController вы можете добавить UISwipeGestureRecognizer, чтобы вызвать действие pop:

.h файл:

#import <UIKit/UIKit.h>

@interface CNavigationController : UINavigationController

@end

.m файл:

#import "CNavigationController.h"

@interface CNavigationController ()<UIGestureRecognizerDelegate, UINavigationControllerDelegate>

@property (nonatomic, retain) UISwipeGestureRecognizer *swipeGesture;

@end

@implementation CNavigationController

#pragma mark - View cycles

- (void)viewDidLoad {
    [super viewDidLoad];

    __weak CNavigationController *weakSelf = self;
    self.delegate = weakSelf;

    self.swipeGesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(gestureFired:)];
    [self.view addGestureRecognizer:self.swipeGesture]; }

#pragma mark - gesture method

-(void)gestureFired:(UISwipeGestureRecognizer *)gesture {
    if (gesture.direction == UISwipeGestureRecognizerDirectionRight)
    {
        [self popViewControllerAnimated:YES];
    } }

#pragma mark - UINavigation Controller delegate

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
    self.swipeGesture.enabled = NO;
    [super pushViewController:viewController animated:animated]; }

#pragma mark UINavigationControllerDelegate

- (void)navigationController:(UINavigationController *)navigationController
       didShowViewController:(UIViewController *)viewController
                    animated:(BOOL)animate {
    self.swipeGesture.enabled = YES; }

@end

Ответ 5

Создайте распознаватель жестов панорамирования и переместите цели интерактивного распознавателя поп-жестов.

Добавьте свой распознаватель в контроллер push-view viewDidLoad и вуаля!

let popGestureRecognizer = self.navigationController!.interactivePopGestureRecognizer!
if let targets = popGestureRecognizer.value(forKey: "targets") as? NSMutableArray {
  let gestureRecognizer = UIPanGestureRecognizer()
  gestureRecognizer.setValue(targets, forKey: "targets")
  self.view.addGestureRecognizer(gestureRecognizer)
}

Ответ 6

я достиг этого, используя эту библиотеку https://github.com/ykyouhei/KYDrawerController я изменил ширину экрана на _drawerWidth = self.view.bounds.size.width; который работал так, как я хотел