从导航栈中移除视图控制器

115

我有一个导航栈,包含5个UIViewController。我希望在第5个UIViewController上的按钮点击事件中移除栈中的第3个和第4个视图控制器。是否可能?如果是,如何实现?

16个回答

187

使用此代码并享受:

NSMutableArray *navigationArray = [[NSMutableArray alloc] initWithArray: self.navigationController.viewControllers];

// [navigationArray removeAllObjects];    // This is just for remove all view controller from navigation stack.
[navigationArray removeObjectAtIndex: 2];  // You can pass your index here
self.navigationController.viewControllers = navigationArray;
[navigationArray release];

希望这能对你有所帮助。

编辑:Swift代码

guard let navigationController = self.navigationController else { return }
var navigationArray = navigationController.viewControllers // To get all UIViewController stack as Array
navigationArray.remove(at: navigationArray.count - 2) // To remove previous UIViewController
self.navigationController?.viewControllers = navigationArray

编辑:移除除了最后一个以外的所有视图控制器 -> 在左上角没有“返回”按钮

guard let navigationController = self.navigationController else { return }
var navigationArray = navigationController.viewControllers // To get all UIViewController stack as Array
let temp = navigationArray.last
navigationArray.removeAll()
navigationArray.append(temp!) //To remove all previous UIViewController except the last one
self.navigationController?.viewControllers = navigationArray

我已经尝试过了,但不起作用。有人告诉我与属性有关,导致它无法释放视图控制器。 - Noah Passalacqua
1
这在 iOS 7 之前的版本中可行,但在 iOS 7 中会导致奇怪的行为。 - Ben H
1
非常适用于iOS 8! - Evan R
4
维韦克:请展示一下你尝试过什么,并且在否定投票之前三思而后行,要有礼貌。 - Nitin
9
这种方法确实从堆栈中删除了一个视图控制器,但似乎还有一个导航项堆栈不受影响。在iOS 8.4中我得到的行为是这样的:假设我们有控制器1、2、3、4、5。我删除了4,而5上显示的返回按钮没有受到影响。我点击返回,它显示3,但标题却是4的。我再次点击返回,它显示3,标题为3。 - Radu Simionescu
显示剩余5条评论

53
你可以先获取数组中的所有视图控制器,然后根据相应的视图控制器类进行检查,最后删除你想要的视图控制器。以下是一小段代码:
NSArray* tempVCA = [self.navigationController viewControllers];

for(UIViewController *tempVC in tempVCA)
{
    if([tempVC isKindOfClass:[urViewControllerClass class]])
    {
        [tempVC removeFromParentViewController];
    }
}

我认为这会让你的工作更容易。


这个可以用于多种目的。谢谢 :) - Hemang
10
当我使用此控制器时,它被正确移除。但是当我使用“返回”按钮时,我的导航栏显示已移除视图控制器的信息。是否还有其他人遇到这种奇怪的行为,如何解决? - Robin Ellerkmann
1
@Robin Ellerkmann,你找到那个问题的解决方案了吗?我正在移除视图控制器,但返回按钮仍然留在导航栏上。 - Mehmet Emre Portakal
2
@MehmetEmre 我使用Swift 2.1和self.navigationController?.viewControllers.removeLast()。这对我来说非常有效。 - Robin Ellerkmann
2
当我在第四个视图控制器时,内存为80MB,当注销所有视图控制器时,它们都被删除了。但是内存仍然是80MB,所以内存没有释放。:( - Anil Gupta
显示剩余3条评论

43

Swift 3 & 4/5

self.navigationController!.viewControllers.removeAll()

self.navigationController?.viewControllers.remove(at: "insert here a number")

Swift 2.1

移除所有:

self.navigationController!.viewControllers.removeAll()

移除指定位置:

self.navigationController?.viewControllers.removeAtIndex("insert here a number")

还有一些其他的操作,例如removeFirst、range等。


3
看了你的回答,我对我的项目工作流程有了一个想法。非常感谢。 - Anirudha Mahale
这将删除导航控制器本身,而不是清除视图控制器堆栈。 - Daniel Beltrami

27

Swift 5:

navigationController?.viewControllers.removeAll(where: { (vc) -> Bool in
    if vc.isKind(of: MyViewController.self) || vc.isKind(of: MyViewController2.self) {
        return false
    } else {
        return true
    }
})

8
这一行代码 return !vc.isKind(of: MyViewController.self) && !vc.isKind(of: MyViewController2.self) 就可以完成任务 :-) - Mark

20

Swift 5,Xcode 13

我发现这种方法很简单,只需指定要从导航栈中移除的视图控制器即可。

extension UINavigationController {
    
    func removeViewController(_ controller: UIViewController.Type) {
        if let viewController = viewControllers.first(where: { $0.isKind(of: controller.self) }) {
            viewController.removeFromParent()
        }
    }
}

使用示例:

navigationController.removeViewController(YourViewController.self)

13

使用UINavigationController中的setViewControllers函数是最佳方法。还有 animated 参数可启用动画效果。

func setViewControllers(_ viewControllers: [UIViewController], animated: Bool)

Swift示例,用于回答问题。

func goToFifthVC() {

    var currentVCStack = self.navigationController?.viewControllers
    currentVCStack?.removeSubrange(2...3)

    let fifthVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "fifthVC")
    currentVCStack?.append(fifthVC)

    self.navigationController?.setViewControllers(currentVCStack!, animated: true)
}

我尝试过其他方式,例如 [tempVC removeFromParentViewController];。它会产生奇怪的行为,当返回上一级页面时,导航栏仍然显示,就像 @robin-ellerkmann 报告的那样。


5
这实际上是最佳解决方案:从navigationController?.viewControllers数组中删除VC并使用setViewControllers指定新的数组。我还检查了僵尸对象或引用循环,这样做是安全的。 - OhadM
我确认这是一个非常好的解决方案:实际上,我正在使用setViewControllers(_:animated:)技术来弹出多个控制器和推送多个控制器。 - Cœur

8

Swift 2.0:

  var navArray:Array = (self.navigationController?.viewControllers)!
  navArray.removeAtIndex(navArray.count-2)
  self.navigationController?.viewControllers = navArray

2
所以你不需要强制解包导航控制器,你可以将它放在一个if语句中 if var navArray = ... { ... } - Kiley

7

详细信息

  • Swift 5.1,Xcode 11.3.1

解决方案

extension UIViewController {
    func removeFromNavigationController() { navigationController?.removeController(.last) { self == $0 } }
}

extension UINavigationController {
    enum ViewControllerPosition { case first, last }
    enum ViewControllersGroupPosition { case first, last, all }

    func removeController(_ position: ViewControllerPosition, animated: Bool = true,
                          where closure: (UIViewController) -> Bool) {
        var index: Int?
        switch position {
            case .first: index = viewControllers.firstIndex(where: closure)
            case .last: index = viewControllers.lastIndex(where: closure)
        }
        if let index = index { removeControllers(animated: animated, in: Range(index...index)) }
    }

    func removeControllers(_ position: ViewControllersGroupPosition, animated: Bool = true,
                           where closure: (UIViewController) -> Bool) {
        var range: Range<Int>?
        switch position {
            case .first: range = viewControllers.firstRange(where: closure)
            case .last:
                guard let _range = viewControllers.reversed().firstRange(where: closure) else { return }
                let count = viewControllers.count - 1
                range = .init(uncheckedBounds: (lower: count - _range.min()!, upper: count - _range.max()!))
            case .all:
                let viewControllers = self.viewControllers.filter { !closure($0) }
                setViewControllers(viewControllers, animated: animated)
                return
        }
        if let range = range { removeControllers(animated: animated, in: range) }
    }

    func removeControllers(animated: Bool = true, in range: Range<Int>) {
        var viewControllers = self.viewControllers
        viewControllers.removeSubrange(range)
        setViewControllers(viewControllers, animated: animated)
    }

    func removeControllers(animated: Bool = true, in range: ClosedRange<Int>) {
        removeControllers(animated: animated, in: Range(range))
    }
}

private extension Array {
    func firstRange(where closure: (Element) -> Bool) -> Range<Int>? {
        guard var index = firstIndex(where: closure) else { return nil }
        var indexes = [Int]()
        while index < count && closure(self[index]) {
            indexes.append(index)
            index += 1
        }
        if indexes.isEmpty { return nil }
        return Range<Int>(indexes.min()!...indexes.max()!)
    }
}

使用方法

removeFromParent()

navigationController?.removeControllers(in: 1...3)

navigationController?.removeController(.first) { $0 != self }

navigationController?.removeController(.last) { $0 != self }

navigationController?.removeControllers(.all) { $0.isKind(of: ViewController.self) }

navigationController?.removeControllers(.first) { !$0.isKind(of: ViewController.self) }

navigationController?.removeControllers(.last) { $0 != self }

完整示例

不要忘记在此粘贴解决方案代码

import UIKit

class ViewController2: ViewController {}

class ViewController: UIViewController {

    private var tag: Int = 0
    deinit { print("____ DEINITED: \(self), tag: \(tag)" ) }

    override func viewDidLoad() {
        super.viewDidLoad()
        print("____ INITED: \(self)")
        let stackView = UIStackView()
        stackView.axis = .vertical
        view.addSubview(stackView)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true

        stackView.addArrangedSubview(createButton(text: "Push ViewController() white", selector: #selector(pushWhiteViewController)))
        stackView.addArrangedSubview(createButton(text: "Push ViewController() gray", selector: #selector(pushGrayViewController)))
        stackView.addArrangedSubview(createButton(text: "Push ViewController2() green", selector: #selector(pushController2)))
        stackView.addArrangedSubview(createButton(text: "Push & remove previous VC", selector: #selector(pushViewControllerAndRemovePrevious)))
        stackView.addArrangedSubview(createButton(text: "Remove first gray VC", selector: #selector(dropFirstGrayViewController)))
        stackView.addArrangedSubview(createButton(text: "Remove last gray VC", selector: #selector(dropLastGrayViewController)))
        stackView.addArrangedSubview(createButton(text: "Remove all gray VCs", selector: #selector(removeAllGrayViewControllers)))
        stackView.addArrangedSubview(createButton(text: "Remove all VCs exept Last", selector: #selector(removeAllViewControllersExeptLast)))
        stackView.addArrangedSubview(createButton(text: "Remove all exept first and last VCs", selector: #selector(removeAllViewControllersExeptFirstAndLast)))
        stackView.addArrangedSubview(createButton(text: "Remove all ViewController2()", selector: #selector(removeAllViewControllers2)))
        stackView.addArrangedSubview(createButton(text: "Remove first VCs where bg != .gray", selector: #selector(dropFirstViewControllers)))
        stackView.addArrangedSubview(createButton(text: "Remove last VCs where bg == .gray", selector: #selector(dropLastViewControllers)))
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        if title?.isEmpty ?? true { title = "First" }
    }

    private func createButton(text: String, selector: Selector) -> UIButton {
        let button = UIButton()
        button.setTitle(text, for: .normal)
        button.setTitleColor(.blue, for: .normal)
        button.addTarget(self, action: selector, for: .touchUpInside)
        return button
    }
}

extension ViewController {

    private func createViewController<VC: ViewController>(backgroundColor: UIColor = .white) -> VC {
        let viewController = VC()
        let counter = (navigationController?.viewControllers.count ?? -1 ) + 1
        viewController.tag = counter
        viewController.title = "Controller \(counter)"
        viewController.view.backgroundColor = backgroundColor
        return viewController
    }

    @objc func pushWhiteViewController() {
        navigationController?.pushViewController(createViewController(), animated: true)
    }

    @objc func pushGrayViewController() {
        navigationController?.pushViewController(createViewController(backgroundColor: .lightGray), animated: true)
    }

    @objc func pushController2() {
        navigationController?.pushViewController(createViewController(backgroundColor: .green) as ViewController2, animated: true)
    }

    @objc func pushViewControllerAndRemovePrevious() {
        navigationController?.pushViewController(createViewController(), animated: true)
        removeFromNavigationController()
    }

    @objc func removeAllGrayViewControllers() {
        navigationController?.removeControllers(.all) { $0.view.backgroundColor == .lightGray }
    }

    @objc func removeAllViewControllersExeptLast() {
        navigationController?.removeControllers(.all) { $0 != self }
    }

    @objc func removeAllViewControllersExeptFirstAndLast() {
        guard let navigationController = navigationController, navigationController.viewControllers.count > 1 else { return }
        let lastIndex = navigationController.viewControllers.count - 1
        navigationController.removeControllers(in: 1..<lastIndex)
    }

    @objc func removeAllViewControllers2() {
        navigationController?.removeControllers(.all) { $0.isKind(of: ViewController2.self) }
    }

    @objc func dropFirstViewControllers() {
        navigationController?.removeControllers(.first) { $0.view.backgroundColor != .lightGray }
    }

    @objc func dropLastViewControllers() {
        navigationController?.removeControllers(.last) { $0.view.backgroundColor == .lightGray }
    }

    @objc func dropFirstGrayViewController() {
        navigationController?.removeController(.first) { $0.view.backgroundColor == .lightGray }
    }

    @objc func dropLastGrayViewController() {
        navigationController?.removeController(.last) { $0.view.backgroundColor == .lightGray }
    }
}

结果

输入图像描述


6

如果你想从第五个视图控制器跳转到第二个视图控制器(跳过第三和第四个),你可以使用 [self.navigationController popToViewController:secondViewController]

你可以从导航控制器堆栈中获取 secondViewController

secondViewController =  [self.navigationController.viewControllers objectAtIndex:yourViewControllerIndex];

1
不想弹出当前的视图控制器。当前的视图控制器应该保持不变。但是我需要弹出它下面堆栈中的2个视图控制器。 - Jean Paul
@JeanPaulScott。我想知道你为什么要这样做,难道不是为了弹出吗?! - Vignesh
有这样一种情况,我可能会将同一个视图控制器的不同实例推入堆栈中。因此,当创建新实例并将其推入堆栈时,我希望弹出先前的实例和与之关联的视图控制器。 - Jean Paul
@Vignesh,由于“滑动返回”手势,在iOS 7中这种方法无法按照要求工作。 - Dennis Pashkov
@JeanPaulScott 要实现你想要的,最安全的方法是在推入新的视图控制器实例之前弹出两次。 - Radu Simionescu

4
请使用以下内容
if let navVCsCount = navigationController?.viewControllers.count {
    navigationController?.viewControllers.removeSubrange(Range(2..<navVCsCount - 1))
}

它将处理navigationController的viewControllers和navigationBar中堆叠的navigationItems。

注意:确保至少在viewDidAppear之后调用它


1
这个方法在 Swift 5 和 Xcode 10.3 中完美运行... 如果导航控制器中的视图控制器个数可以被获取,就执行以下操作: self.navigationController?.viewControllers.removeSubrange(navVCsCount-3..<navVCsCount - 1) - Kedar Sukerkar

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