ฉันจะแสดงมุมมองจาก UINavigationController และแทนที่ด้วยการดำเนินการอื่นในการดำเนินการเดียวได้อย่างไร


84

ฉันมีแอปพลิเคชันที่ฉันต้องการลบมุมมองหนึ่งออกจากสแต็กของ UINavigationController และแทนที่ด้วยอีกมุมมองหนึ่ง สถานการณ์คือมุมมองแรกสร้างรายการที่แก้ไขได้จากนั้นแทนที่ตัวเองด้วยตัวแก้ไขสำหรับรายการ เมื่อฉันทำวิธีแก้ปัญหาที่ชัดเจนในมุมมองแรก:

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

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

ฉันมีพฤติกรรมแปลก ๆ โดยปกติมุมมองตัวแก้ไขจะปรากฏขึ้น แต่ถ้าฉันพยายามใช้ปุ่มย้อนกลับบนแถบนำทางฉันได้รับหน้าจอพิเศษบางส่วนว่างเปล่าและบางส่วนก็เมา ชื่อเรื่องจะสุ่มเกินไป มันเหมือนกับว่าสแต็ก nav ถูกเลื่อนออกไปอย่างสมบูรณ์

อะไรจะเป็นแนวทางที่ดีกว่าสำหรับปัญหานี้?

ขอบคุณ Matt

คำตอบ:


137

ฉันค้นพบว่าคุณไม่จำเป็นต้องยุ่งกับviewControllersทรัพย์สินเลย โดยทั่วไปมี 2 สิ่งที่ยุ่งยากเกี่ยวกับเรื่องนี้

  1. self.navigationControllerจะกลับมาnilถ้าselfไม่ได้อยู่บนสแต็กของตัวควบคุมการนำทาง ดังนั้นให้บันทึกลงในตัวแปรภายในเครื่องก่อนที่คุณจะสูญเสียการเข้าถึงไป
  2. คุณต้องretain(และถูกต้องrelease) selfไม่เช่นนั้นออบเจ็กต์ที่เป็นเจ้าของเมธอดที่คุณอยู่จะถูกยกเลิกการจัดสรรทำให้เกิดความแปลกประหลาด

เมื่อคุณเตรียมการดังกล่าวแล้วก็ป๊อปและดันตามปกติ รหัสนี้จะแทนที่ตัวควบคุมด้านบนด้วยตัวควบคุมอื่นทันที

// 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];

ในบรรทัดสุดท้ายว่าถ้าคุณเปลี่ยนanimatedไปYESแล้วหน้าจอใหม่จริงจะเคลื่อนไหวในและควบคุมที่คุณเพิ่งโผล่ออกมาจะเคลื่อนไหว สวยดี!


แจ่ม! ทางออกที่ดีกว่ามาก
emmby

น่ากลัว แม้ว่าฉันไม่จำเป็นต้องเรียก [[self คง] autorelease] แต่ก็ยังใช้งานได้ดี
iamj4de

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 เคลื่อนไหว: ใช่]; [UIView กระทำแอนิเมชั่น];
Martin

11
ใช้งานได้ดีกับ ARC เพียงแค่ลบบรรทัดการรักษา / ปล่อยอัตโนมัติ
Ian Terrell

2
@TomerPeled ใช่คำตอบนี้มีอายุเกือบ 5 ปีแล้ว ... ฉันคิดว่าเป็นเช่นนั้นใน iOS 3 API มีการเปลี่ยนแปลงมากพอที่ฉันไม่แน่ใจว่าเป็นคำตอบที่ดีที่สุด
Alex Wayne

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

เมื่อใช้วิธีนี้คุณจะเขียนทับป๊อปโอเวอร์ และเพื่อแสดงใน 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

สิ่งที่ฉันกำลังมองหา
JERC

9

จากประสบการณ์คุณจะต้องซอกับviewControllersคุณสมบัติของ UINavigationController โดยตรง สิ่งนี้ควรใช้งานได้:

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 กำลังส่งคืนค่าศูนย์หลังจากที่ฉันลบอ็อบเจ็กต์สุดท้ายออกจากอาร์เรย์คอนโทรลเลอร์ ฉันคิดว่าเป็นเพราะบรรทัดนี้ในเอกสารสำหรับ UIViewController ในอินสแตนซ์วิธีการ navigationController "ส่งคืนตัวควบคุมการนำทางเท่านั้นหากตัวควบคุมมุมมองอยู่ในสแต็ก"

ฉันคิดว่าเมื่อตัวควบคุมมุมมองปัจจุบันถูกลบออกจากสแต็กเมธอด navigationController จะคืนค่าศูนย์

นี่คือรหัสปรับปรุงที่ใช้งานได้:

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

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 คุณต้องทำสิ่งต่อไปนี้:

         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

เมื่อเร็ว ๆ นี้ฉันต้องทำสิ่งที่คล้ายกันและใช้วิธีแก้ปัญหาของฉันตามคำตอบของมิคาเอล ในกรณีของฉันฉันต้องลบ View Controllers สองตัวออกจาก Navigation Stack จากนั้นเพิ่ม View Controller ใหม่บน โทร

[ตัวควบคุม removeLastObject];
สองครั้งทำงานได้ดีในกรณีของฉัน

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

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

จากตัวควบคุมมุมมองของคุณคุณสามารถแทนที่มุมมองด้านบนด้วยมุมมองใหม่ได้ดังนี้:

[self.navigationController replaceTopViewControllerWithViewController: newController];

0

คุณสามารถตรวจสอบด้วยอาร์เรย์ตัวควบคุมมุมมองการนำทางซึ่งคุณให้ตัวควบคุมมุมมองทั้งหมดที่คุณเพิ่มในสแตกการนำทาง ด้วยการใช้อาร์เรย์นั้นคุณสามารถย้อนกลับไปยังตัวควบคุมมุมมองเฉพาะได้


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ที่จะnilหลังpopViewControllerAnimated

แค่ป๊อปแล้วดันเข้าใจง่ายไม่ต้องเข้าviewControllers....

ใน ViewController ของคุณ


0

ไม่ใช่คำตอบที่แน่นอน แต่อาจช่วยได้ในบางสถานการณ์ (เช่นของฉัน):

หากคุณต้องการป๊อปวิวคอนโทรลเลอร์ C และไปที่ B (นอกสแต็ก) แทนที่จะเป็น A (ตัวที่ร้อง C) เป็นไปได้ที่จะกด B ก่อน C และมีทั้ง 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.