UINavigationController에서 뷰를 팝하고 한 작업에서 다른 뷰로 대체하려면 어떻게해야합니까?


84

UINavigationController 스택에서 하나의 뷰를 제거하고 다른 뷰로 교체해야하는 응용 프로그램이 있습니다. 상황은 첫 번째보기가 편집 가능한 항목을 만든 다음 항목에 대한 편집기로 자신을 대체하는 것입니다. 첫 번째보기 내에서 명백한 해결책을 수행 할 때 :

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

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

나는 매우 이상한 행동을한다. 일반적으로 편집기보기가 나타나지만 탐색 모음에서 뒤로 버튼을 사용하려고하면 추가 화면이 표시되고 일부는 비어 있으며 일부는 망가졌습니다. 제목도 무작위가됩니다. 내비 스택이 완전히 튀어 나온 것과 같습니다.

이 문제에 대한 더 나은 접근 방법은 무엇입니까?

고마워, 맷

답변:


137

나는 당신이 viewControllers재산을 전혀 손으로 엉망으로 만들 필요가 없다는 것을 발견 했습니다. 기본적으로 이것에 대해 두 가지 까다로운 점이 있습니다.

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

그 마지막 줄에서 당신은 변경하는 경우 animatedYES다음 새 화면이 실제로 애니메이션에서 방금 튀어 컨트롤러가 밖으로 애니메이션 것입니다 것입니다. 꽤 좋아 보인다!


훌륭한! 훨씬 더 나은 솔루션
emmby

대박. [[자체 보유] autorelease]를 호출 할 필요는 없었지만 여전히 잘 작동합니다.
iamj4de 2010-06-18

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 animated : YES]; [UIView commitAnimations];
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

경험상 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에 대한 설명서에서이 줄이 원인이라고 생각합니다. "뷰 컨트롤러가 스택에있는 경우에만 탐색 컨트롤러를 반환합니다."

현재 뷰 컨트롤러가 스택에서 제거되면 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

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

나는 최근에 비슷한 일을해야했고 Michaels 대답에 내 솔루션을 기반으로했습니다. 제 경우에는 탐색 스택에서 두 개의 뷰 컨트롤러를 제거한 다음 새 뷰 컨트롤러를 추가해야했습니다. 부름

[컨트롤러 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.navigationControllernilpopViewControllerAnimated

그냥 팝앤 푸쉬, 이해하기 쉬우 며 액세스 할 필요가 없습니다 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 (스택 외부)로 이동해야하는 경우 B를 C보다 먼저 밀어 스택에 3 개를 모두 넣을 수 있습니다. B 푸시를 보이지 않게 유지하고 C 만 표시할지 C와 B를 모두 표시할지 선택하면 동일한 효과를 얻을 수 있습니다.

초기 문제 A-> C (C를 팝하고 스택에서 B를 표시하고 싶습니다)

가능한 솔루션 A-> B (보이지 않는 밀기)-> C (C를 눌렀을 때 B를 표시하거나 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.