Навигация поп-вид при прокрутке вправо, например, приложение 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;
который работал так, как я хотел