web-dev-qa-db-ja.com

Instagram iPhoneアプリのように右にスワイプするとナビゲーションポップビューが表示されます。これを実現するにはどうすればよいですか?

画面を右にスワイプしたときにビューをポップしたい、またはナビゲーションバーの戻るボタンのように機能します。

私は使っている:

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

ポップナビゲーションビューのこの1行のコードは私にとってはうまくいきますが、画面の中央からスワイプすると、InstagramiPhoneアプリのようには機能しません。

ここでは、右にスワイプしてポップナビゲーションビューの例を見ることができるInstagramアプリの1つの画面を示します。

enter image description here

17
chetu

「右にスワイプしてVCをポップ」のAppleの自動実装は、画面の左〜20ポイントでのみ機能します。このようにして、アプリの機能を台無しにしないようにします。画面にUIScrollViewがあり、VCが飛び出し続けるため、右にスワイプできないとします。これは素敵じゃないか。

Appleは言う ここ

interactivePopGestureRecognizer

トップビューコントローラーをナビゲーションスタックからポップするジェスチャレコグナイザー。 (読み取り専用)

@property(nonatomic、readonly)UIGestureRecognizer * InteractivePopGestureRecognizer

ナビゲーションコントローラーは、このジェスチャレコグナイザーをビューにインストールし、それを使用して、ナビゲーションスタックから最上位のビューコントローラーをポップします。このプロパティを使用して、ジェスチャレコグナイザーを取得し、ユーザーインターフェイスの他のジェスチャレコグナイザーの動作に関連付けることができます。ジェスチャレコグナイザーを結合するときは、ジェスチャレコグナイザーがイベントを処理する機会が与えられるように、ジェスチャレコグナイザーが同時に認識されるようにしてください。

したがって、独自のUIGestureRecognizerを実装し、その動作をinteractivePopGestureRecognizerUIViewControllerに関連付ける必要があります。


編集:

これが私が構築したソリューションです。 UIViewControllerAnimatedTransitioningデリゲートに準拠した独自の遷移を実装できます。このソリューションは機能しますが、徹底的にテストされていません。

interactiveスライディングトランジションを取得して、ViewControllerをポップします。ビューのどこからでも右にスライドできます。

既知の問題:パンを開始し、ビューの幅の半分より前に停止すると、遷移がキャンセルされます(予期される動作)。このプロセス中に、ビューは元のフレームにリセットされます。それらは、このアニメーション中の視覚的な不具合です。

例のクラスは次のとおりです。

UINavigationController> ViewController> SecondViewController

CustomPopTr​​ansition.h

#import <Foundation/Foundation.h>

@interface CustomPopTransition : NSObject <UIViewControllerAnimatedTransitioning>

@end

CustomPopTr​​ansition.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's 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's 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
13
rdurand

これがSwiftバージョンのSpynetの回答ですが、いくつかの変更が加えられています。まず、UIViewアニメーションの線形曲線を定義しました。次に、セミアニメーションを追加しました。効果を高めるために、下のビューに透明な黒い背景を表示します。3番目に、UINavigationControllerをサブクラス化しました。これにより、UINavigationController内の任意の「ポップ」トランジションにトランジションを適用できます。コードは次のとおりです。

CustomPopTr​​ansition.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's 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

5
Tometoyou

このために独自のソリューションを作成する必要はありません。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
jwswart

UINavigationControllerをサブクラス化すると、UISwipeGestureRecognizerを追加してポップアクションをトリガーできます。

。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
3
Arun

パンジェスチャ認識機能を作成し、インタラクティブなポップジェスチャ認識機能のターゲットを移動します。

プッシュされたViewControllerの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)
}
2
Kugutsumen

このライブラリを使用してこれを実現しました https://github.com/ykyouhei/KYDrawerController 画面の幅を_drawerWidth = self.view.bounds.size.widthに変更しました。私が望むように働いた

0
Pratik Jamariya