画面を右にスワイプしたときにビューをポップしたい、またはナビゲーションバーの戻るボタンのように機能します。
私は使っている:
self.navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;
ポップナビゲーションビューのこの1行のコードは私にとってはうまくいきますが、画面の中央からスワイプすると、InstagramiPhoneアプリのようには機能しません。
ここでは、右にスワイプしてポップナビゲーションビューの例を見ることができるInstagramアプリの1つの画面を示します。
「右にスワイプしてVCをポップ」のAppleの自動実装は、画面の左〜20ポイントでのみ機能します。このようにして、アプリの機能を台無しにしないようにします。画面にUIScrollView
があり、VCが飛び出し続けるため、右にスワイプできないとします。これは素敵じゃないか。
Appleは言う ここ :
interactivePopGestureRecognizer
トップビューコントローラーをナビゲーションスタックからポップするジェスチャレコグナイザー。 (読み取り専用)
@property(nonatomic、readonly)UIGestureRecognizer * InteractivePopGestureRecognizer
ナビゲーションコントローラーは、このジェスチャレコグナイザーをビューにインストールし、それを使用して、ナビゲーションスタックから最上位のビューコントローラーをポップします。このプロパティを使用して、ジェスチャレコグナイザーを取得し、ユーザーインターフェイスの他のジェスチャレコグナイザーの動作に関連付けることができます。ジェスチャレコグナイザーを結合するときは、ジェスチャレコグナイザーがイベントを処理する機会が与えられるように、ジェスチャレコグナイザーが同時に認識されるようにしてください。
したがって、独自のUIGestureRecognizer
を実装し、その動作をinteractivePopGestureRecognizer
のUIViewController
に関連付ける必要があります。
編集:
これが私が構築したソリューションです。 UIViewControllerAnimatedTransitioning
デリゲートに準拠した独自の遷移を実装できます。このソリューションは機能しますが、徹底的にテストされていません。
interactiveスライディングトランジションを取得して、ViewControllerをポップします。ビューのどこからでも右にスライドできます。
既知の問題:パンを開始し、ビューの幅の半分より前に停止すると、遷移がキャンセルされます(予期される動作)。このプロセス中に、ビューは元のフレームにリセットされます。それらは、このアニメーション中の視覚的な不具合です。
例のクラスは次のとおりです。
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'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
これがSwiftバージョンのSpynetの回答ですが、いくつかの変更が加えられています。まず、UIView
アニメーションの線形曲線を定義しました。次に、セミアニメーションを追加しました。効果を高めるために、下のビューに透明な黒い背景を表示します。3番目に、UINavigationController
をサブクラス化しました。これにより、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'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
}
}
}
このために独自のソリューションを作成する必要はありません。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
}
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
パンジェスチャ認識機能を作成し、インタラクティブなポップジェスチャ認識機能のターゲットを移動します。
プッシュされた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)
}
このライブラリを使用してこれを実現しました https://github.com/ykyouhei/KYDrawerController 画面の幅を_drawerWidth = self.view.bounds.size.widthに変更しました。私が望むように働いた