替换UINavigationController层次结构中的一个UIViewController

36

我有一个 iPhone 应用程序,使用标准的 UINavigationController 实现应用程序导航。

我正在尝试找出一种替换层次结构中视图控制器的方法。换句话说,我的应用程序加载到一个 rootViewController,当用户按下按钮时,应用程序会推到 firstViewController。然后用户再按下一个按钮来导航应用程序到 secondViewController。同样地,用户向下导航到另一个视图控制器 thirdViewController。然而,我希望 thirdViewController 的“返回”按钮弹回到 firstViewController

基本上,当用户推到 thirdViewController 时,我想要它替换导航层次结构中的 secondViewController

这有可能吗? 我知道这可以使用 Three20,但这不是我的情况。尽管如此,如果在 Three20 中可行,那么使用 SDK 调用肯定也可以。有人有什么想法吗?

谢谢, Brett


我也会研究Unwind Segues,它们对于完成这种事情非常有用。在这里可以找到关于它们的很棒的SO答案:https://dev59.com/SGcs5IYBdhLWcg3w0HIa - jrisberg
在给定的例子中,你不是可以简单地弹出(这将摆脱第二个),然后推入(现在你有1-3)。或者,如果是“不同的按钮”,当你在1-2-3上时,只需弹出两次,你就会回到1。 - Fattie
10个回答

60

很简单,当要推送第三个视图控制器时,不要使用简单的pushViewController,而是这样做:

NSArray * viewControllers = [self.navigationController viewControllers];
NSArray * newViewControllers = [NSArray arrayWithObjects:[viewControllers objectAtIndex:0], [viewControllers objectAtIndex:1], thirdController,nil];
[self.navigationController setViewControllers:newViewControllers];

[viewControllers objectAtIndex:0][viewControllers objectAtIndex:1]分别是您的rootViewController和FirstViewController。


在这段代码之后,我还需要调用 pushViewController 吗? - Rod

23
NSMutableArray *viewController = [NSMutableArray arrayWithArray:[navController viewControllers]];
[viewController replaceObjectAtIndex:1 withObject:replacementController];
[navController setViewControllers:viewController];

请查看UINavigationController类参考文档以获取更多信息。


谢谢,伙计。这个解决方案处理得不错。干杯。 - Felipe

21

在Swift中更加清晰的方法应该是:

extension UINavigationController {
  func replaceTopViewController(with viewController: UIViewController, animated: Bool) {
    var vcs = viewControllers
    vcs[vcs.count - 1] = viewController
    setViewControllers(vcs, animated: animated)
  }
}

4

如果只是在导航控制器的视图控制器数组中简单地替换viewController,则无法动画过渡。我建议在第三个视图控制器的viewWillAppear方法中执行以下操作。

-(void) viewWillAppear:(BOOL)animated
{
   NSArray *vCs=[[self navigationController] viewControllers];
   NSMutableArray *nvCs=nil;
   //remove the view controller before the current view controller
   nvCs=[[NSMutableArray alloc]initWithArray:vCs];
   [nvCs removeObjectAtIndex:([nvCs count]-2)];
   [[self navigationController] setViewControllers:nvCs];
   [super viewWillAppear:animated];
}

3

Swift 4版本:

if var viewControllers = navigationController?.viewControllers {
    viewControllers[viewControllers.count - 1] = newViewController
    navigationController?.viewControllers = viewControllers
}

2
- (void)swapTopViewController:(UIViewController *)topViewController{
    NSArray *viewControllers = [self.navigationController viewControllers];
    NSMutableArray *editableViewControllers = [NSMutableArray arrayWithArray:viewControllers];
    [editableViewControllers removeLastObject];
    [editableViewControllers addObject:topViewController];
    [self.navigationController setViewControllers:editableViewControllers];
}

2

由于您只是尝试从ViewControllers堆栈中弹出两次,因此您可能可以通过调用相同的方法来获得相同的结果。

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

从第三个视图控制器的后退按钮


2

Swift 5 UINavigationController扩展,支持在替换时使用Fade动画。

extension UINavigationController {
    func find(of type: UIViewController.Type) -> UIViewController? {
        for controller in self.viewControllers {
            if controller.isKind(of: type) {
                return controller
            }
        }
        return nil
    }
    
    func go(to controller: UIViewController, animated: Bool = true) {
        if let old = self.find(of: type(of: controller)) {
            self.popToViewController(old, animated: animated)
        }
        else {
            self.pushViewController(controller, animated: animated)
        }
    }
    
    fileprivate func prepareFade() {
        let transition: CATransition = CATransition()
        transition.duration = 0.3
        transition.type = CATransitionType.fade
        view.layer.add(transition, forKey: nil)
    }
    
    func replace(to controller: UIViewController, animated: Bool = true, fade: Bool = true) {
        guard let current = self.topViewController else {
            return
        }
        
        var controllers = self.viewControllers
        controllers.remove(current)
        controllers.append(controller)
        
        if fade {
            prepareFade()
            self.setViewControllers(controllers, animated: false)
        }
        else {
            self.setViewControllers(controllers, animated: animated)
        }
    }
}

extension Array where Element: Equatable {
    mutating func remove(_ element: Element) {
        if let index = self.firstIndex(of: element) {
            self.remove(at: index)
        }
    }
}

1
extension UINavigationController {

    func setTop(viewController: UIViewController) {
        viewControllers = [viewController]
    }

}

那样做会把它们全部删除,只留下一个吗? - Fattie
@Fattie 是的。这对于 OP 的情况已经足够了。该函数设置顶部视图控制器。如果您不想要这种行为,那么您需要一个不同的函数。 - Elijah
1
明白了。不错的东西。 - Fattie

0

通常您需要将堆栈清除到“Base”,并将新的放在上面:

@duan的答案完美地用一个新项替换了顶部项:

extension UINavigationController {
    func replaceTopViewController(with vc: UIViewController, animated: Bool) {
        var vcs = viewControllers
        vcs[vcs.count - 1] = vc // NB watch for bizarre zero size
        setViewControllers(vcs, animated: animated)
    }
}

然而,很多时候,您会有一个“基础”控制器,您希望它始终存在。

如果您想要安全地将所有内容清除为基础内容,并更改为新内容(在基础内容中不运行viewWillAppear),那么:

extension UINavigationController {
    func popToRoot(andPushOnly: UIViewController) {
        var vcs = viewControllers
        while vcs.count > 1 { vcs.removeLast() }
        if vcs.count < 1 { return }
        vcs.append(andPushOnly)
        setViewControllers(vcs, animated: false)
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接