UINavigationControllerからビューをポップして、1回の操作で別のビューに置き換えるにはどうすればよいですか?


84

UINavigationControllerのスタックから1つのビューを削除して、別のビューに置き換える必要があるアプリケーションがあります。状況は、最初のビューが編集可能なアイテムを作成し、次にそれ自体をアイテムのエディターに置き換えることです。私が最初のビュー内で明白な解決策を実行するとき:

MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];

[self retain];
[self.navigationController popViewControllerAnimated: NO];
[self.navigationController pushViewController: mevc animated: YES];
[self release];

私は非常に奇妙な行動をします。通常はエディタービューが表示されますが、ナビゲーションバーの[戻る]ボタンを使用しようとすると、余分な画面が表示され、一部が空白になり、一部が台無しになります。タイトルもランダムになります。ナビゲーションスタックが完全にホースで固定されているようなものです。

この問題へのより良いアプローチは何でしょうか?

ありがとう、マット

回答:


137

viewControllersプロパティを手動でいじる必要がまったくないことを発見しました。基本的に、これには2つの注意が必要です。

  1. self.navigationController現在ナビゲーションコントローラのスタックにないnil場合selfは戻ります。したがって、アクセスできなくなる前に、ローカル変数に保存してください。
  2. あなたがしなければならないretain(そして適切にreleaseselfまたはあなたがいるメソッドを所有するオブジェクトは割り当てが解除され、奇妙さを引き起こします。

その準備をしたら、通常どおりポップしてプッシュします。このコードは、すぐにトップコントローラーを別のコントローラーに置き換えます。

// locally store the navigation controller since
// self.navigationController will be nil once we are popped
UINavigationController *navController = self.navigationController;

// retain ourselves so that the controller will still exist once it's popped off
[[self retain] autorelease];

// Pop this controller and replace with another
[navController popViewControllerAnimated:NO];
[navController pushViewController:someViewController animated:NO];

その最後の行で、をに変更するanimatedYES、新しい画面が実際にアニメーション化され、ポップしたばかりのコントローラーがアニメーション化されます。かなり素敵に見えます!


鮮やかさ!はるかに優れたソリューション
emmby 2010

驚くばかり。[[self hold] autorelease]を呼び出す必要はありませんでしたが、それでも問題なく動作します。
iamj4de 2010年

4
おそらく明らかな追加ですが、上記のコードをアニメーションブロックに配置して、遷移をアニメーション化できます。[UIView beginAnimations:@ "View Flip" context:nil]; [UIView setAnimationDuration:0.80]; [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:navController.view cache:NO]; [navController pushViewController:newControllerアニメーション:YES]; [UIView commitAnimations];
マーティン

11
保持/自動解放ラインを削除するだけで、ARCでうまく機能します。
Ian Terrell 2012年

2
@TomerPeledええ、この答えは5年近く前のものです... iOS 3のようにそうだったと思います。APIが十分に変更されたため、これが最良の答えかどうかはわかりません。
アレックスウェイン

56

次のアプローチは私にはより良いように思え、ARCでもうまく機能します。

UIViewController *newVC = [[UIViewController alloc] init];
// Replace the current view controller
NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:[[self navigationController] viewControllers]];
[viewControllers removeLastObject];
[viewControllers addObject:newVC];
[[self navigationController] setViewControllers:viewControllers animated:YES];

1
@LukeRogers、これにより、次の警告が表示されます。予期しない状態でナビゲーション遷移を終了します。ナビゲーションバーのサブビューツリーが破損する可能性があります。それを抑制する方法はありますか?
zaitsman 2013

このソリューションを使用して、ポップオーバーを上書きします。また、DetailViewに表示するには、コードは次のようになりますif(indexPath.row == 0){UIViewController *newVC = [[UIViewController alloc] init];newVC = [self.storyboard instantiateViewControllerWithIdentifier:@"Item1VC"]; NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:[_detailViewController.navigationController viewControllers]]; [viewControllers removeLastObject];[viewControllers addObject:newVC]; [_detailViewController.navigationController setViewControllers:viewControllers animated:YES];}
。– LAOMUSIC ARTS 2013

私が探していたもの。
JERC

9

経験から、UINavigationControllerのviewControllersプロパティを直接いじる必要があります。このようなものが機能するはずです:

MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];

[[self retain] autorelease];
NSMutableArray *controllers = [[self.navigationController.viewControllers mutableCopy] autorelease];
[controllers removeLastObject];
self.navigationController.viewControllers = controllers;
[self.navigationController pushViewController:mevc animated: YES];

注:保持/解放を保持/自動解放に変更しました。これは、一般的に堅牢であるためです。保持/解放の間に例外が発生した場合、自己リークしますが、自動解放で処理されます。


7

多くの努力(そしてKevinからのコードの微調整)の後、私はついにスタックからポップされているビューコントローラーでこれを行う方法を理解しました。私が抱えていた問題は、コントローラー配列から最後のオブジェクトを削除した後、self.navigationControllerがnilを返すことでした。インスタンスメソッドnavigationControllerのUIViewControllerのドキュメントのこの行が原因だと思います。「ViewControllerがスタックにある場合にのみNavigationControllerを返します。」

現在のViewControllerがスタックから削除されると、そのnavigationControllerメソッドはnilを返すと思います。

動作する調整済みコードは次のとおりです。

UINavigationController *navController = self.navigationController;
MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];

NSMutableArray *controllers = [[self.navigationController.viewControllers mutableCopy] autorelease];
[controllers removeLastObject];
navController.viewControllers = controllers;
[navController pushViewController:mevc animated: YES];

これは私にブラックホールを与えます!
LAOMUSIC ARTS 2013

4

おかげで、これはまさに私が必要としていたものでした。また、これをアニメーションに入れて、ページをカールさせます。

        MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];

    UINavigationController *navController = self.navigationController;      
    [[self retain] autorelease];

    [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration: 0.7];
    [UIView setAnimationTransition:<#UIViewAnimationTransitionCurlDown#> forView:navController.view cache:NO];

    [navController popViewControllerAnimated:NO];
    [navController pushViewController:mevc animated:NO];

    [UIView commitAnimations];

0.6の継続時間は高速で、3GS以降に適していますが、0.8はまだ3Gには少し速すぎます。

ヨハン


あなたのコードはまさに私が使用したものです、素晴らしいです!ありがとう。注:ページカールトランジションを使用すると、ビューの下部に白いアーティファクトが表示されます(理由はわかります)が、フリップを使用すると問題なく機能しました。とにかく、これは素晴らしくコンパクトなコードです!
David H

3

popToRootViewControllerで他のViewControllerを表示する場合は、次の手順を実行する必要があります。

         UIViewController *newVC = [[WelcomeScreenVC alloc] initWithNibName:@"WelcomeScreenVC" bundle:[NSBundle mainBundle]];
            NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:[[self navigationController] viewControllers]];
            [viewControllers removeAllObjects];
            [viewControllers addObject:newVC];
            [[self navigationController] setViewControllers:viewControllers animated:NO];

これで、以前のスタックがすべて削除され、必要なrootViewControllerを使用して新しいスタックが作成されます。


1

私は最近同様のことをしなければならず、マイケルズの答えに基づいて解決策を見つけました。私の場合、ナビゲーションスタックから2つのView Controllerを削除してから、に新しいViewControllerを追加する必要がありました。呼び出し

[コントローラーremoveLastObject];
2回、私の場合は問題なく動作しました。

UINavigationController *navController = self.navigationController;

// retain ourselves so that the controller will still exist once it's popped off
[[self retain] autorelease];

searchViewController = [[SearchViewController alloc] init];    
NSMutableArray *controllers = [[self.navigationController.viewControllers mutableCopy] autorelease];

[controllers removeLastObject];
// In my case I want to go up two, then push one..
[controllers removeLastObject];
navController.viewControllers = controllers;

NSLog(@"controllers: %@",controllers);
controllers = nil;

[navController pushViewController:searchViewController animated: NO];


1

このUINavigationControllerインスタンスメソッドは機能する可能性があります...

指定されたビューコントローラがトップビューコントローラになるまでビューコントローラをポップしてから、表示を更新します。

- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated

1

これは、viewControllers配列を直接操作する必要のない別のアプローチです。コントローラがまだポップされているかどうかを確認し、ポップされている場合はプッシュします。

TasksViewController *taskViewController = [[TasksViewController alloc] initWithNibName:nil bundle:nil];

if ([navigationController.viewControllers indexOfObject:taskViewController] == NSNotFound)
{
    [navigationController pushViewController:taskViewController animated:animated];
}
else
{
    [navigationController popToViewController:taskViewController animated:animated];
}

1
NSMutableArray *controllers = [self.navigationController.viewControllers mutableCopy];
    for(int i=0;i<controllers.count;i++){
       [controllers removeLastObject];
    }
 self.navigationController.viewControllers = controllers;

これにより、コンソールに警告が表示されます-予期しない状態でナビゲーション遷移を終了します。ナビゲーションバーのサブビューツリーが破損する可能性があります。それを抑制する方法はありますか?
zaitsman 2013

1

それを行うための私のお気に入りの方法は、UINavigationControllerのカテゴリを使用することです。以下が機能するはずです。

UINavigationController + Helpers.h #import

@interface UINavigationController (Helpers)

- (UIViewController*) replaceTopViewControllerWithViewController: (UIViewController*) controller;

@end

UINavigationController + Helpers.m
#import "UINavigationController + Helpers.h"

@implementation UINavigationController (Helpers)

- (UIViewController*) replaceTopViewControllerWithViewController: (UIViewController*) controller {
    UIViewController* topController = self.viewControllers.lastObject;
    [[topController retain] autorelease];
    UIViewController* poppedViewController = [self popViewControllerAnimated:NO];
    [self pushViewController:controller animated:NO];
    return poppedViewController;
}

@end

次に、View Controllerから、次のようにして上面ビューを新しいものに置き換えることができます。

[self.navigationController replaceTopViewControllerWithViewController: newController];

0

ナビゲーションスタックに追加したすべてのビューコントローラーを提供するナビゲーションビューコントローラー配列で確認できます。その配列を使用することで、特定のViewControllerに戻ることができます。


0

monotouch / xamarin IOSの場合:

UISplitViewControllerクラス内。

UINavigationController mainNav = this._navController; 
//List<UIViewController> controllers = mainNav.ViewControllers.ToList();
mainNav.ViewControllers = new UIViewController[] { }; 
mainNav.PushViewController(detail, true);//to have the animation

0

または、

あなたは後になることcategoryを避けるためself.navigationControllerに使用することができますnilpopViewControllerAnimated

ポップアンドプッシュするだけで、理解しやすく、アクセスする必要はありませんviewControllers...。

// UINavigationController+Helper.h
@interface UINavigationController (Helper)

- (UIViewController*) popThenPushViewController:(UIViewController *)viewController animated:(BOOL)animated;

@end


// UINavigationController+Helper.m
@implementation UINavigationController (Helper)

- (UIViewController*) popThenPushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    UIViewController *v =[self popViewControllerAnimated:NO];

    [self pushViewController:viewController animated:animated];

    return v;
}
@end

ViewControllerで

// #import "UINavigationController+Helper.h"
// invoke in your code
UIViewController *v= [[MyNewViewController alloc] init];

[self.navigationController popThenPushViewController:v animated:YES];

RELEASE_SAFELY(v);

0

正確には答えではありませんが、いくつかのシナリオ(たとえば私のもの)では役立つかもしれません:

ビューコントローラCをポップして、A(Cの下にあるもの)ではなくB(スタック外)に移動する必要がある場合は、Cの前にBをプッシュして、3つすべてをスタックに置くことができます。Bプッシュを非表示にし、Cのみをポップするか、CとBだけをポップするかを選択することで、同じ効果を得ることができます。

初期の問題A-> C(スタックからCをポップしてBを表示したい)

考えられる解決策A-> B(プッシュされた非表示)-> C(Cをポップするとき、Bを表示するか、ポップするかを選択します)


0

私はこのソリューションを使用してアニメーションを保持します。

[self.navigationController pushViewController:controller animated:YES];
NSMutableArray *newControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
[newControllers removeObject:newControllers[newControllers.count - 2]];
[self.navigationController setViewControllers:newControllers];
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.