从导航堆栈中删除视图控制器

Posted

技术标签:

【中文标题】从导航堆栈中删除视图控制器【英文标题】:Removing viewcontrollers from navigation stack 【发布时间】:2012-05-04 02:58:07 【问题描述】:

我有一个导航堆栈,比如 5 个 UIViewController。我想通过单击第 5 个视图控制器中的按钮来删除堆栈中的第 3 和第 4 个视图控制器。是否有可能做到这一点?如果有怎么办?

【问题讨论】:

【参考方案1】:

Swift 5.4

删除SpecificViewController

navigationController.viewControllers.removeAll  $0 is SpecificViewController 

【讨论】:

【参考方案2】:

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)

【讨论】:

【参考方案3】:

使用此代码并享受:

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

编辑:移除除最后一个之外的所有 ViewController -> 左上角没有返回按钮

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

【讨论】:

我已经绑定了这个并且不起作用。有人告诉我,与属性有关的事情导致它不释放视图控制器。 这在 ios 适用于 iOS 8! Vivek : 告诉我你尝试过什么,并有礼貌地在投反对票之前考虑一下。 此方法确实从堆栈中删除了一个视图控制器,但似乎还有一个不受影响的导航项堆栈。我在 ios 8.4 中得到的行为是这样的:假设我们有控制器 1 2 3 4 5。我删除了 4,显示在 5 上的后退按钮不受影响。我点击返回,它显示 3 但标题为 4。我再次点击返回,它显示 3,标题为 3【参考方案4】:

// 按类名从堆栈中删除视图控制器,然后关闭当前视图。

 self.navigationController?.viewControllers.removeAll(where:  (vc) -> Bool in
      if vc.isKind(of: ViewController.self) || vc.isKind(of: ViewController2.self) 
       
        return true
         
     else 
        
         return false
         
        )
self.navigationController?.popViewController(animated: false)
self.dismiss(animated: true, completion: nil)

【讨论】:

【参考方案5】:

详情

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】:

斯威夫特 5:

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

【讨论】:

return !vc.isKind(of: MyViewController.self) &amp;&amp; !vc.isKind(of: MyViewController2.self) 会在一行中完成这项工作:-)【参考方案7】:

Swift 5.1,Xcode 11

extension UINavigationController
public func removePreviousController(total: Int)
    let totalViewControllers = self.viewControllers.count
    self.viewControllers.removeSubrange(totalViewControllers-total..<totalViewControllers - 1)

确保在前一个控制器的 viewDidDisappear() 或新控制器的 viewDidAppear() 之后调用此实用程序函数

【讨论】:

【参考方案8】:

除非另有说明,否则我使用方法编写了一个扩展,该方法删除了 root 和 top 之间的所有控制器。

extension UINavigationController 
func removeControllers(between start: UIViewController?, end: UIViewController?) 
    guard viewControllers.count > 1 else  return 
    let startIndex: Int
    if let start = start 
        guard let index = viewControllers.index(of: start) else 
            return
        
        startIndex = index
     else 
        startIndex = 0
    

    let endIndex: Int
    if let end = end 
        guard let index = viewControllers.index(of: end) else 
            return
        
        endIndex = index
     else 
        endIndex = viewControllers.count - 1
    
    let range = startIndex + 1 ..< endIndex
    viewControllers.removeSubrange(range)

如果你想使用范围(例如:2 到 5)你可以使用

    let range = 2 ..< 5
    viewControllers.removeSubrange(range)

在 iOS 12.2、Swift 5 上测试

【讨论】:

【参考方案9】:

Swift 3 & 4/5

self.navigationController!.viewControllers.removeAll()

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

斯威夫特 2.1

全部删除:

self.navigationController!.viewControllers.removeAll()

在索引处删除

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

还有很多可能的操作,例如 removeFirst、range 等。

【讨论】:

看了你的回答,我对我的项目工作流程有了一个想法。非常感谢。 这会删除它自己的 NavigationController,而不是清理一堆视图控制器【参考方案10】:

使用UINavigationController 中的setViewControllers 函数是最好的方法。还有animated参数可以开启动画。

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

快速提问示例

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];。它做出了奇怪的行为,删除了 ViewController 导航,当弹出时仍然显示 @robin-ellerkmann 报告的那样

【讨论】:

这实际上是最好的解决方案:从navigationController?.viewControllers 数组中移除VC,并使用setViewControllers 分配新数组。我还检查了僵尸或参考循环,它是安全的。 我确认这是一个很好的解决方案:我实际上在两个方面都使用了 setViewControllers(_:animated:) 技术:弹出多个控制器和推送多个控制器。【参考方案11】:

这个解决方案在 swift 4 中对我有用:

let VCCount = self.navigationController!.viewControllers.count
self.navigationController?.viewControllers.removeSubrange(Range(VCCount-3..<VCCount - 1))

您当前在堆栈中的视图控制器索引是:

self.navigationController!.viewControllers.count - 1

【讨论】:

【参考方案12】:

使用这个

if let navVCsCount = navigationController?.viewControllers.count 
    navigationController?.viewControllers.removeSubrange(Range(2..<navVCsCount - 1))

它会处理 navigationController 的 ViewControllers。 viewControllers 以及一个在 navigationBar 中堆叠的 navigationItems。

注意:请务必至少在 viewDidAppear 之后调用它

【讨论】:

这种方法在 Swift 5、Xcode 10.3 中对我来说非常有效......如果让 navVCsCount = navigationController?.viewControllers.count self.navigationController?.viewControllers.removeSubrange(navVCsCount-3..【参考方案13】:

Swift 2.0:

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

【讨论】:

所以你不是强制解开导航控制器,你可以让它成为一个 if 语句 if var navArray = ... ... 【参考方案14】:

你可以先获取数组中的所有视图控制器,然后与对应的视图控制器类核对后,就可以删除你想要的了。

这是一小段代码:

NSArray* tempVCA = [self.navigationController viewControllers];

for(UIViewController *tempVC in tempVCA)

    if([tempVC isKindOfClass:[urViewControllerClass class]])
    
        [tempVC removeFromParentViewController];
    

我认为这将使您的工作更轻松。

【讨论】:

这个可以多用途。谢谢:) 当我使用它时,控制器被正确移除。但是当我使用“返回”按钮时,我的导航栏会显示已删除的 viewController 的信息。有其他人收到这种奇怪的行为吗?我该如何解决? @Robin Ellerkmann 你找到解决这个问题的方法了吗?我正在删除视图控制器,但后退按钮仍保留在导航栏上。 @MehmetEmre 我使用 Swift 2.1 和 self.navigationController?.viewControllers.removeLast()。这对我来说效果很好。 当我在 4 视图控制器中时,注销时所有视图控制器的内存为 80MB。内存还是80MB。所以内存没有释放。 :(【参考方案15】:

如果您尝试从第 5 个视图控制器移动到第 2 个视图控制器(跳过第 3 个和第 4 个),您想使用[self.navigationController popToviewController:secondViewController]

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

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

【讨论】:

不想弹出当前的视图控制器。当前的视图控制器应该保持不变。但我需要在堆栈中弹出它下面的 2 个视图控制器 @JeanPaulScott。我想知道你为什么要这样做,如果不是为了弹出?!。 有一种情况,我会将同一个视图控制器的不同实例推入堆栈。所以当一个新实例被创建并推入堆栈时,我想弹出前一个实例和与之关联的视图控制器。 @Vignesh 由于“滑动弹出”手势,这在 iOS 7 中无法按要求工作 @JeanPaulScott 来实现你想要的,最安全的方法是在推送你的新视图控制器实例之前弹出两次。

以上是关于从导航堆栈中删除视图控制器的主要内容,如果未能解决你的问题,请参考以下文章

如何从导航控制器中删除视图

Ios 导航 - 自定义后退按钮或从堆栈中删除视图控制器?

斯威夫特/iOS。从导航堆栈中删除几个视图控制器

以编程方式从 ARC 中的导航堆栈中删除 viewController

从堆栈释放视图,带有导航控制器的标签栏

从导航堆栈中弹出视图控制器时从右侧滑入