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

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

150647 次浏览

Use this code and enjoy:

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

Hope this will help you.

Edit: Swift Code

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

Edit: To remove all ViewController except last one -> no Back Button in the upper left corner

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

You can first get all the view controllers in the array and then after checking with the corresponding view controller class, you can delete the one you want.

Here is small piece of code:

NSArray* tempVCA = [self.navigationController viewControllers];


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

I think this will make your work easier.

If you are trying to move to 2nd view controller from 5th view controller (skipping 3rd and 4th), you would like to use [self.navigationController popToviewController:secondViewController].

You can obtain the secondViewController from the navigation controller stack.

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

Swift 3 & 4/5

self.navigationController!.viewControllers.removeAll()

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

Swift 2.1

remove all:

self.navigationController!.viewControllers.removeAll()

remove at index

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

There a bunch of more possible actions like removeFirst,range etc.

Swift 2.0:

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

Use this

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

It will take care of ViewControllers of navigationController. viewControllers and also a navigationItems stacked in navigationBar.

Note: Be sure to call it at least after viewDidAppear

This solution worked for me in swift 4:

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

your current view controller index in stack is:

self.navigationController!.viewControllers.count - 1

Using setViewControllers function from UINavigationController is the best way. There is also animated parameter to enable animation.

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

Example in swift for question

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)
}

I tried other ways like [tempVC removeFromParentViewController];. It make weird behaviour, removed ViewController navigation still showing when pop back like reported by @robin-ellerkmann

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
}
})

Swift 5.1, Xcode 11

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

Make sure to call this utility function after viewDidDisappear() of previous controller or viewDidAppear() of new controller

I wrote an extension with method which removes all controllers between root and top, unless specified otherwise.

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)
}

}

If you want to use range (for example: 2 to 5) you can just use

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

Tested on iOS 12.2, Swift 5

Swift 5, Xcode 13

I found this approach simple by specifying which view controller(s) you want to remove from the navigation stack.

extension UINavigationController {
    

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

Example use:

navigationController.removeViewController(YourViewController.self)

Details

  • Swift 5.1, Xcode 11.3.1

Solution

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()!)
}
}

Usage

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 }

Full Sample

Do not forget to paste here the solution code

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 }
}
}

Result

enter image description here

// removing the viewcontrollers by class names from stack and then dismissing the current view.

 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)

Swift 5.4

removing SpecificViewController

navigationController.viewControllers.removeAll { $0 is SpecificViewController }

You should remove the controllers from navigation controller array and ALSO their parents

 navigationController?.viewControllers.removeAll(where: { (vc) -> Bool in
if vc.isKind(of: MyViewController.self)
|| vc.isKind(of: MyViewControllerType2.self)
|| vc.isKind(of: MyViewControllerType3.self) {


/* remove from parent */
vc.removeFromParent()
return true
} else {
return false
}
})