web-dev-qa-db-ja.com

UINavigationController popToRootViewControllerをクリックし、すぐに新しいビューをプッシュします

2つのタブを持つtabBarControllerがあり、最初のタブにはNavigatorControllerのインスタンスが含まれています。 navigatorControllerは、tableView上のすべてのネットワークピアを一覧表示するカスタムviewController「peersViewController」で開始されます。ピアを選択すると、「FilesListViewController」(c:\ディレクトリ内のファイルを一覧表示する)のインスタンスがnavigationControllerスタックにプッシュされます。

このfilesListViewControllerには、ドキュメントディレクトリに移動するためのボタンがあります。これを行うには、rootViewControllerのgotoDirectory:(NSString *)pathメソッドを呼び出すようにインターフェイスを配線しました。

- (void)gotoDirectory:(NSString*)path {
     [[self navigationController] popToRootViewControllerAnimated:YES];
     NSArray *files = [self getFilesFromPeerAtPath:path];
     FilesListViewController *filesVC = [[FilesListViewController alloc] initWithFiles:files];
     [[self navigationController] pushViewController:filesVC animated:YES];
     [filesVC release];
}

ただし、そのボタンを押すと、navigationControllerはビューをルートView Controllerにポップしましたが、インスタンス化したFilesListViewControllerは表示されませんでした。ログから、カスタムinitWithFilesメソッドが実際に呼び出され、ネットワーク関連のものがファイル名を取得したことがわかります。

他の何かがこれについて厄介です。 2番目のタブをクリックしてから、最初のタブに戻って、hualaを試してみました。必要なファイル名はそこにあります。データとfilesListViewControllerが実際にnavigatorControllerスタックにプッシュされたように見えますが、表示は更新されず、rootViewController(peersViewController)の画面でスタックしました。

私は何か間違ったことをしていますか?

-ベン。

-質問を投稿してから15分後に編集しました。回避策を見つけましたが、ポップしてからプッシュが機能しないのが気になります。

- (void)gotoDirectory:(NSString*)path {
     PeersListViewController *rootViewController = (PeersListViewController*)[[[self navigationController] viewControllers] objectAtIndex:0];
     [[self navigationController] setViewControllers:[NSArray arrayWithObject:rootViewController]];
     FilesListViewController *filesVC = [[FilesListViewController alloc] initWithFiles:files];
     [[self navigationController] pushViewController:filesVC animated:YES];
     [filesVC release];
}

この方法でnavigationControllerを回避する必要はないようです。おそらく、元のスタックにあったすべてのviewControllerを解放する必要があります。ただし、これはiphone3.0シミュレーターでは機能します。

ただし、このコードを使用している場合、メモリ解放はどのように処理する必要がありますか?ビューコントローラの元のNSArrayを取得して、すべてを解放する必要がありますか?

23
Ben

この問題の問題と解決策は、実際には非常に単純です。

[self.navigationController popToRootViewControllerAnimated:YES]を呼び出すと、self.navigationControllernilに設定されます。その後[self.navigationController pushViewController:someOtherViewController]を呼び出すと、事実上nilにメッセージが送信されますが、これは何もしません。

回避するには、navigationControllerへのローカル参照を設定し、代わりにそれを使用します。

UINavigationController * navigationController = self.navigationController;
[navigationController popToRootViewControllerAnimated:NO];
[navigationController pushViewController:someOtherViewController animated:YES];

Jasonが述べたように、これが正しく機能するには、popToRootViewControllerをアニメーションなしで実行する必要があります。

これを指摘してくれた jpimbert on Apple forums に感謝します。

84
Nick Street

非常によく似た問題が発生しました(ただし、タブを使用していません)。

3つのviewController : main(root)、フォーム、結果を取得しました。 UINavigationControllerスタックが

"main -> result"

btnClickで、popToRootViewControllerAnimatedを実行してから、formViewCtrlをプッシュします。持っているために

"main -> form"

ナビゲーションバーのタイトルと戻るボタンのラベルが正しく、formViewCtrlのイベントが呼び出されます。しかし、私はまだメインビューを見ています。

これが私の「解決策」です

いくつかのテストを行った後、私はrootViwCtrlに移動するアニメーションなしでこれは正常に機能するであることがわかりました。そのため、アニメーションを使用してviewCtrlをプッシュするだけです。

iPhone 3.0、デバイスとシミュレータで問題が見つかりました。

何か新しいものがあれば、投稿を更新/コメントします。

10
Loda

ルートにポップしてから新しいViewControllerをプッシュすることに関するこの質問はかなり一般的であり、この投稿は頻繁に表示されるので、他の新しい人、特にXcode4とストーリーボードを使用している人を助けるために少し追加したいと思いました。

Xcode 4には、ストーリーボードがあります。次のビューコントローラがあるとします:HomeViewController、FirstPageViewController、SecondPageViewController。必ずそれぞれをクリックし、[ユーティリティ]ペイン-> [属性インスペクター]に移動して識別子に名前を付けてください。それらの名前は、Home、First、およびSecondと言います。

あなたはホームです、次にファーストに行き、次にセカンドに行き、戻るボタンを押してホームに戻ることができるようにしたいです。これを行うには、FirstPageViewControllerのコードを変更します。

例を拡張するには、ストーリーボードのFirstPageViewControllerにボタンを作成します。 Ctrlキーを押しながらそのボタンをFirstPageViewController.mにドラッグします。そこでは、次のコードが目的の結果を達成します。

    // Remember to add #import "SecondPageViewController.h" at the top
    SecondPageViewController *secondView = [self.storyboard instantiateViewContorllerWithIdentifier:@"Second"];
    UINavigationController *navigationController = self.navigationController;
    NSArray *array = [navigationController viewControllers];
    // [array objectAtIndex:0] is the root view controller
    NSArray *viewControllersStack = [NSArray arrayWithObjects:[array objectAtIndex:0], secondView, nil];
    [navigationController setViewControllers:viewControllersStack animated:YES];

基本的には、View Controllerを取得し、必要な順序でスタックに配置してから、ナビゲーションコントローラーにそのスタックをナビゲーションに使用させることになります。これは、プッシュとポップの代替手段です。

4
Jack

回避策を見つけましたが、なぜ機能するのか説明できません。1。最初に必要なコントローラーをプッシュします。 2.次に、目的の場所にポップします。

これは完全に非論理的ですが、私の場合はうまくいきます。明確にするために、私は次のシナリオでそれを使用しています:最初の画面->ロード画面に移動-> 2番目の画面2番目の画面にいるとき、ロード画面をスタックに入れたくないクリックして戻ると、最初の画面に移動する必要があります。

よろしく、ベスココレフ

1
Vesko Kolev

Nick Streetの答えは、popToRootViewControllerを実行してから、別のVCをプッシュする場合に役立ちます。

VC1-> VC2-> VC3:VC3から戻るボタンを押す=> VC2、次にVC1、ここではOK

ただし、VC1がVC2をプッシュし、次にVC3がプッシュされると、VC3から直接VC1に戻ることは期待どおりに機能しません。

VC3の-(void)viewWillDisappear:(BOOL)animatedに実装しました:

-(void)viewWillDisappear:(BOOL)animated{

    ...
    [self.navigationController popToRootViewControllerAnimated:YES];
}

また、「戻るボタン」に実装しようとしましたが、同じ結果です。VC3から戻るボタンを押してVC1に戻ると、壊れます。実際のVCはVC1ですが、ナビゲーションバーはVC2のままです。他の組み合わせで遊んでいると、VC2にVC1のnavBarが表示されます。

Lodaはタイミングについて何か言及しました。それがここでの主な問題だと思います。私はいくつかのことを試したので、ここで何かを見逃しているかもしれませんが、これが私にとってついにうまくいったことです:

VC3の場合:

-(void)viewWillDisappear:(BOOL)animated {

    [super viewWillDisappear:animated];
    // notify VC2
    [[NSNotificationCenter defaultCenter] postNotificationName:backFromV3 object:self];
}

VC2の場合:

-(void)viewDidLoad {

    ...

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(backFromV3)
                                                 name:@"BackFromV3"
                                               object:nil];
}

-(void)backFromV3{
    [NSTimer scheduledTimerWithTimeInterval:0.5 
                                 target:self
                               selector:@selector(backToRootViewController)
                               userInfo:nil
                                repeats:NO];
}

-(void)backToVC1 {
    self.navigationItem.rightBarButtonItem = nil;
    [self.navigationController popToRootViewControllerAnimated:YES];
}

もちろん、必要な清掃を行ってください。

ここではタイマーが重要です。 0の場合、壊れます。 0.5は大丈夫のようです。

それは私にとって完璧に機能します。少し重いですが、うまくいくものは何も見つかりませんでした。

1
Adriano Lucas

基本的に、ポップアニメーションが完了するまでプッシュアニメーションを遅らせることで、実際に「戻る」アニメーションを維持し、続いて「進む」アニメーションを維持することができます。次に例を示します。

(注:appDelegateに "transitionTo"というNSString変数があり、最初は@ ""に設定されています)...まず、後で検出できるように、その変数をNSStringに設定します。次に、コントローラーをポップして、ルートに戻る素敵な画面遷移を提供します。

appDelegate.transitionTo = @"Another";
[detailNavigationController popToRootViewControllerAnimated:YES];

次に、rootviewcontrollerのクラス内で、viewDidAppearメソッドを使用します。

-(void)viewDidAppear:(BOOL)animated
{
    AppDelegate *appDelegate =(AppDelegate*) [UIApplication sharedApplication].delegate;
    if([appDelegate.transitionTo isEqualToString:@"Another"])
    {
        [self transitionToAnotherView];
        appDelegate.transitionTo = @"";
    }
}

-(void)transitionToAnotherView
{
    // Create and Push new view controller here
    AnotherViewController *controller = [[AnotherViewController alloc] init];

    UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:@"Home" style:UIBarButtonItemStyleBordered target:nil action:nil];
    [self.navigationItem setBackBarButtonItem:backButton];

    [[self navigationController] pushViewController:controller animated:YES];
}

したがって、基本的には、ルートにポップします...遷移が「viewDidAppear」で終了したら...次に次のビューをプッシュします。たまたま、どのビューに移行するかを示す変数を保持しました(@ ""は、この画面にとどまりたい場合に移行を行わないことを意味します)。

1
Highrule