4

UINavigationController (モーダル、プッシュ) にいくつかのビュー コントローラーが埋め込まれており、スワイプ ジェスチャを使用してそれらをナビゲートしています。

// Gesture recognizers
UISwipeGestureRecognizer *downGesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(dismissButton)];
downGesture.direction = UISwipeGestureRecognizerDirectionDown;
[downGesture setCancelsTouchesInView:NO];
[self.view addGestureRecognizer:downGesture];

これは問題なく動作しますが、ユーザーがモーダルに表示されたビュー コントローラーを物理的にドラッグできるようにしたいと考えています。戻るボタンをタップする代わりに、前のビューに戻ります。

ビューでパンジェスチャを使用してこれを実装しようとしましたが、もちろん、以前のビューコントローラーはその背後に表示されません。この効果はどのように適切に達成されますか? ビューコントローラーの封じ込めとは?もしそうなら、いくつかのView Controllerをスタックにプッシュするとき、それはどのように機能しますか? 私が話しているナビゲーションのタイプの例は、LetterPress アプリにあります。

ありがとう。

4

1 に答える 1

9

はい、カスタム コンテナー ビューが最適です (iOS 5 以降)。基本的に、ビルトインchildViewControllersプロパティを使用して独自のカスタム コンテナーを作成し、すべての子ビュー コントローラーを追跡します。currentChildIndex現在使用している子コントローラーを追跡するために、独自のプロパティ、たとえば が必要になる場合があります。

@property (nonatomic) NSInteger currentChildIndex;

親コントローラーには、おそらく次のような、スワイプに関連しないナビゲーション用のいくつかのプッシュおよびポップ メソッドが必要です。

- (void)pushChildViewController:(UIViewController *)newChildController
{
    // remove any other children that we've popped off, but are still lingering about

    for (NSInteger index = [self.childViewControllers count] - 1; index > self.currentChildIndex; index--)
    {
        UIViewController *childController = self.childViewControllers[index];
        [childController willMoveToParentViewController:nil];
        [childController.view removeFromSuperview];
        [childController removeFromParentViewController];
    }

    // get reference to the current child controller

    UIViewController *currentChildController = self.childViewControllers[self.currentChildIndex];

    // set new child to be off to the right

    CGRect frame = self.containerView.bounds;
    frame.origin.x += frame.size.width;
    newChildController.view.frame = frame;

    // add the new child

    [self addChildViewController:newChildController];
    [self.containerView addSubview:newChildController.view];
    [newChildController didMoveToParentViewController:self];

    [UIView animateWithDuration:0.5
                     animations:^{
                         CGRect frame = self.containerView.bounds;
                         newChildController.view.frame = frame;
                         frame.origin.x -= frame.size.width;
                         currentChildController.view.frame = frame;
                     }];

    self.currentChildIndex++;
}

- (void)popChildViewController
{
    if (self.currentChildIndex == 0)
        return;

    UIViewController *currentChildController = self.childViewControllers[self.currentChildIndex];
    self.currentChildIndex--;
    UIViewController *previousChildController = self.childViewControllers[self.currentChildIndex];

    CGRect onScreenFrame = self.containerView.bounds;

    CGRect offToTheRightFrame = self.containerView.bounds;
    offToTheRightFrame.origin.x += offToTheRightFrame.size.width;

    [UIView animateWithDuration:0.5
                     animations:^{
                         currentChildController.view.frame = offToTheRightFrame;
                         previousChildController.view.frame = onScreenFrame;
                     }];
}

個人的には、これら 2 つのメソッドに対して定義されたプロトコルがあり、親コントローラーがそのプロトコルに準拠するように構成されていることを確認します。

@protocol ParentControllerDelegate <NSObject>

- (void)pushChildViewController:(UIViewController *)newChildController;
- (void)popChildViewController;

@end

@interface ParentViewController : UIViewController <ParentControllerDelegate>
...
@end

次に、子が新しい子をプッシュしたい場合、次のように実行できます。

ChildViewController *controller = ... // instantiate and configure your next controller however you want to do that

id<ParentControllerDelegate> parent = (id)self.parentViewController;
NSAssert([parent conformsToProtocol:@protocol(ParentControllerDelegate)], @"Parent must conform to ParentControllerDelegate");

[parent pushChildViewController:controller];

子供が飛び降りたいときは、次のようにできます。

id<ParentControllerDelegate> parent = (id)self.parentViewController;
NSAssert([parent conformsToProtocol:@protocol(ParentControllerDelegate)], @"Parent must conform to ParentControllerDelegate");

[parent popChildViewController];

次に、親ビュー コントローラーにはパン ジェスチャが設定されており、ある子から別の子へのユーザーのパンを処理します。

- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
    static UIView *currentView;
    static UIView *previousView;
    static UIView *nextView;

    if (gesture.state == UIGestureRecognizerStateBegan)
    {
        // identify previous view (if any)

        if (self.currentChildIndex > 0)
        {
            UIViewController *previous = self.childViewControllers[self.currentChildIndex - 1];
            previousView = previous.view;
        }
        else
        {
            previousView = nil;
        }

        // identify next view (if any)

        if (self.currentChildIndex < ([self.childViewControllers count] - 1))
        {
            UIViewController *next = self.childViewControllers[self.currentChildIndex + 1];
            nextView = next.view;
        }
        else
        {
            nextView = nil;
        }

        // identify current view

        UIViewController *current = self.childViewControllers[self.currentChildIndex];
        currentView = current.view;
    }

    // if we're in the middle of a pan, let's adjust the center of the views accordingly

    CGPoint translation = [gesture translationInView:gesture.view.superview];

    previousView.transform = CGAffineTransformMakeTranslation(translation.x, 0.0);
    currentView.transform = CGAffineTransformMakeTranslation(translation.x, 0.0);
    nextView.transform = CGAffineTransformMakeTranslation(translation.x, 0.0);

    // if we're all done, let's animate the completion (or if we didn't move far enough,
    // the reversal) of the pan gesture

    if (gesture.state == UIGestureRecognizerStateEnded ||
        gesture.state == UIGestureRecognizerStateCancelled ||
        gesture.state == UIGestureRecognizerStateFailed)
    {

        CGPoint center = currentView.center;
        CGPoint currentCenter = CGPointMake(center.x + translation.x, center.y);
        CGPoint offRight = CGPointMake(center.x + currentView.frame.size.width, center.y);
        CGPoint offLeft = CGPointMake(center.x - currentView.frame.size.width, center.y);

        CGPoint velocity = [gesture velocityInView:gesture.view.superview];

        if ((translation.x + velocity.x * 0.5) < (-self.containerView.frame.size.width / 2.0) && nextView)
        {
            // if we finished pan to left, reset transforms

            previousView.transform = CGAffineTransformIdentity;
            currentView.transform = CGAffineTransformIdentity;
            nextView.transform = CGAffineTransformIdentity;

            // set the starting point of the animation to pick up from where
            // we had previously transformed the views

            CGPoint nextCenter = CGPointMake(nextView.center.x + translation.x, nextView.center.y);
            currentView.center = currentCenter;
            nextView.center = nextCenter;

            // and animate the moving of the views to their final resting points,
            // adjusting the currentChildIndex appropriately

            [UIView animateWithDuration:0.25
                                  delay:0.0
                                options:UIViewAnimationOptionCurveEaseOut
                             animations:^{
                                 currentView.center = offLeft;
                                 nextView.center = center;
                                 self.currentChildIndex++;
                             }
                             completion:NULL];
        }
        else if ((translation.x + velocity.x * 0.5) > (self.containerView.frame.size.width / 2.0) && previousView)
        {
            // if we finished pan to right, reset transforms

            previousView.transform = CGAffineTransformIdentity;
            currentView.transform = CGAffineTransformIdentity;
            nextView.transform = CGAffineTransformIdentity;

            // set the starting point of the animation to pick up from where
            // we had previously transformed the views

            CGPoint previousCenter = CGPointMake(previousView.center.x + translation.x, previousView.center.y);
            currentView.center = currentCenter;
            previousView.center = previousCenter;

            // and animate the moving of the views to their final resting points,
            // adjusting the currentChildIndex appropriately

            [UIView animateWithDuration:0.25
                                  delay:0.0
                                options:UIViewAnimationOptionCurveEaseOut
                             animations:^{
                                 currentView.center = offRight;
                                 previousView.center = center;
                                 self.currentChildIndex--;
                             }
                             completion:NULL];
        }
        else
        {
            [UIView animateWithDuration:0.25
                                  delay:0.0
                                options:UIViewAnimationOptionCurveEaseInOut
                             animations:^{
                                 previousView.transform = CGAffineTransformIdentity;
                                 currentView.transform = CGAffineTransformIdentity;
                                 nextView.transform = CGAffineTransformIdentity;
                             }
                             completion:NULL];
        }
    }
}

上記で使用した左右のパンではなく、上下のパンを行っているように見えますが、基本的なアイデアを理解していただければ幸いです。


ところで、iOS 6 では、お尋ねのユーザー インターフェイス (ジェスチャを使用したビュー間のスライド) は、組み込みのコンテナー コントローラーを使用してより効率的に実行できる可能性がありますUIPageViewController。のトランジション スタイルUIPageViewControllerTransitionStyleScrollと のナビゲーション方向を使用するだけですUIPageViewControllerNavigationOrientationHorizontal。残念ながら、iOS 5 ではページ カール トランジションしか許可されておらず、Apple は iOS 6 で必要なスクロール トランジションしか導入していませんUIPageViewController。カスタム コンテナの呼び出しを行う必要がなく、ジェスチャ レコグナイザを作成する必要もありません)。

たとえば、「ページ ビュー コントローラー」をストーリーボードにドラッグし、UIPageViewControllerサブクラスを作成しviewDidLoadてから、最初のページを構成する必要があります。

UIViewController *firstPage = [self.storyboard instantiateViewControllerWithIdentifier:@"1"]; // use whatever storyboard id your left page uses
self.viewControllerStack = [NSMutableArray arrayWithObject:firstPage];

[self setViewControllers:@[firstPage]
               direction:UIPageViewControllerNavigationDirectionForward
                animated:NO
              completion:NULL];

self.dataSource = self;

UIPageViewControllerDataSource次に、次のメソッドを定義する必要があります。

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
    if ([viewController isKindOfClass:[LeftViewController class]])
        return [self.storyboard instantiateViewControllerWithIdentifier:@"2"];

    return nil;
}

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
    if ([viewController isKindOfClass:[RightViewController class]])
        return [self.storyboard instantiateViewControllerWithIdentifier:@"1"];

    return nil;
}

実装はさまざまです (少なくとも異なるクラス名と異なるストーリーボード識別子。ユーザーが要求したときに、ページ ビュー コントローラーが次のページのコントローラーをインスタンス化できるようにしています。それらへの強い参照を保持していないため、他のページへの遷移が完了すると解放されます...代わりに、起動時に両方をインスタンス化することもできます。その後、これらbeforeafterルーチンは明らかにインスタンス化されず、配列でそれらを検索します)、うまくいけばあなたは得るでしょうアイデア。

しかし、重要な問題は、ジェスチャ コードやカスタム コンテナー ビュー コントローラー コードなどがないことです。はるかに単純です。

于 2013-04-01T17:06:05.833 に答える