Suppression des contrôleurs de vue de la pile de navigation

92

J'ai une pile de navigation, avec par exemple 5 UIViewControllers. Je veux supprimer les 3e et 4e viewcontrollers de la pile en cliquant sur un bouton dans le 5e viewcontroller. Est-il possible de faire cela? Si c'est le cas, comment?

Jean Paul Scott
la source

Réponses:

167

Utilisez ce code et profitez de:

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

J'espère que ceci vous aidera.

Edit: Code 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
Nitine
la source
J'ai lié cela et ne fonctionne pas. On m'a dit que quelque chose à voir avec les propriétés l'empêchait de désallouer les contrôleurs de vue.
Noah Passalacqua
1
cela a fonctionné sous iOS <7, mais entraîne un comportement étrange sous iOS 7.
Ben H
1
Fonctionne très bien pour iOS 8!
Evan R
4
Vivek: Montrez-moi ce que vous avez essayé et ayez la courtoisie de réfléchir avant un vote négatif.
Nitin
7
cette méthode supprime effectivement un viewcontroller de la pile, mais il semble également y avoir une pile d'éléments de navigation qui n'est pas affectée. Le comportement que j'obtiens dans ios 8.4 est le suivant: disons que nous avons des contrôleurs 1 2 3 4 5. Je supprime 4, le bouton de retour affiché sur 5 n'est pas affecté. Je clique en arrière, il montre 3 mais le titre de 4. Je clique à nouveau, il montre 3 avec le titre de 3
Radu Simionescu
49

Vous pouvez d'abord obtenir tous les contrôleurs de vue dans le tableau, puis après avoir vérifié avec la classe de contrôleur de vue correspondante, vous pouvez supprimer celui que vous voulez.

Voici un petit morceau de code:

NSArray* tempVCA = [self.navigationController viewControllers];

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

Je pense que cela facilitera votre travail.

Sourabh Bhardwaj
la source
Celui-ci peut être utilisé à des fins multiples. Merci :)
Hemang
10
Lorsque j'utilise cela, le contrôleur est correctement retiré. Mais lorsque j'utilise le bouton "Retour", ma barre de navigation affiche les informations du viewController supprimé. Quelqu'un d'autre reçoit-il ce comportement étrange et comment puis-je y remédier?
Robin Ellerkmann
1
@Robin Ellerkmann avez-vous trouvé une solution à ce problème? Je supprime viewcontroller mais le bouton de retour reste dans la barre de navigation.
Mehmet Emre
2
@MehmetEmre J'utilise Swift 2.1 avec self.navigationController? .ViewControllers.removeLast (). Cela fonctionne plutôt bien pour moi.
Robin Ellerkmann le
1
Quand j'étais dans 4 viewcontroller, la mémoire était de 80 Mo lorsque vous vous déconnectez, tous les viewcontroller sont supprimés. Mémoire toujours 80 Mo. Donc, la mémoire ne se libère pas. :(
Anil Gupta
39

Swift 3 et 4/5

self.navigationController!.viewControllers.removeAll()

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

Swift 2.1

Enlever tout:

self.navigationController!.viewControllers.removeAll()

supprimer à l'index

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

Il y a un tas d'autres actions possibles comme removeFirst, range etc.

kuzdu
la source
3
En regardant votre réponse, j'ai eu une idée du flux de travail de mon projet. Merci beaucoup.
Anirudha Mahale
Cela supprime le NavigationController lui-même, ne nettoie pas une pile de contrôleurs de vue
Daniel Beltrami
16

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
    }
})
Niklas
la source
3
return !vc.isKind(of: MyViewController.self) && !vc.isKind(of: MyViewController2.self)ferait le travail en une seule ligne :-)
Mark
10

Utiliser la setViewControllersfonction de UINavigationControllerest la meilleure façon. Il existe également un animatedparamètre pour activer l'animation.

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

Exemple en swift pour 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)
}

J'ai essayé d'autres moyens comme [tempVC removeFromParentViewController];. Cela crée un comportement étrange, la navigation de ViewController supprimée s'affiche toujours lorsqu'elle revient comme indiqué par @ robin-ellerkmann

Thein
la source
5
C'est en fait la meilleure solution: supprimer le VC du tableau navigationController? .ViewControllers et utiliser setViewControllers pour attribuer le nouveau tableau. J'ai également vérifié les zombies ou les cycles de référence, c'est sûr.
OhadM
Je confirme que c'est une excellente solution: j'utilise en fait cette setViewControllers(_:animated:)technique dans les deux sens: pour faire apparaître plusieurs contrôleurs et pousser plusieurs contrôleurs.
Cœur
8

Swift 2.0:

  var navArray:Array = (self.navigationController?.viewControllers)!
  navArray.removeAtIndex(navArray.count-2)
  self.navigationController?.viewControllers = navArray
tahir raees
la source
2
Donc, vous n'êtes pas forcé de déballer le contrôleur de navigation, vous pouvez en faire une déclaration ifif var navArray = ... { ... }
Kiley
6

Swift 5, Xcode 11.3

J'ai trouvé cette approche simple en spécifiant le ou les contrôleurs de vue que vous souhaitez supprimer de la pile de navigation.

extension UINavigationController {

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

Exemple d'utilisation:

navigationController.removeViewController(YourViewController.self)
Mitchell C
la source
5

Si vous essayez de vous déplacer vers le 2ème contrôleur de vue à partir du 5ème contrôleur de vue (en sautant les 3ème et 4ème), vous souhaitez utiliser [self.navigationController popToviewController:secondViewController].

Vous pouvez obtenir le secondViewControllerdepuis la pile du contrôleur de navigation.

secondViewController =  [self.navigationController.viewControllers objectAtIndex:yourViewControllerIndex];
Vignesh
la source
1
Je ne veux pas afficher le contrôleur de vue actuel. Le viewcontroller actuel doit rester intact. Mais j'ai besoin de faire apparaître les 2 contrôleurs de vue qui se trouvent en dessous dans la pile
Jean Paul Scott
@JeanPaulScott. Je me demande pourquoi voudriez-vous faire ça, sinon pour apparaître?!.
Vignesh
Il y a un cas où j'aurais différentes instances du même viewcontroller poussées dans la pile. Ainsi, lorsqu'une nouvelle instance est créée et poussée dans la pile, je veux faire apparaître l'instance précédente et le contrôleur de vue associé à cela.
Jean Paul Scott
@Vignesh Cela ne fonctionnerait pas comme requis dans iOS 7 à cause du geste `` swipe to pop ''
Dennis Pashkov
@JeanPaulScott pour réaliser ce que vous voulez, le plus sûr est de faire apparaître deux fois avant de pousser votre nouvelle instance de contrôleur de vue.
Radu Simionescu
4

Utilisez ceci

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

Il s'occupera des ViewControllers de navigationController. viewControllers et aussi un navigationItems empilés dans navigationBar.

Remarque: assurez-vous de l'appeler au moins après viewDidAppear

Nikola Markovic
la source
1
Cette méthode a parfaitement fonctionné pour moi dans Swift 5, Xcode 10.3 ... if let navVCsCount = navigationController? .ViewControllers.count {self.navigationController? .ViewControllers.removeSubrange (navVCsCount-3 .. <navVCsCount - 1)}
Kedar Sukerkar
2

Cette solution a fonctionné pour moi dans Swift 4:

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

votre index de contrôleur de vue actuel dans la pile est:

self.navigationController!.viewControllers.count - 1
babak
la source
2

Swift 5.1, Xcode 11

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

Assurez-vous d'appeler cette fonction utilitaire après viewDidDisappear () du contrôleur précédent ou viewDidAppear () du nouveau contrôleur

Kedar Sukerkar
la source
1

Détails

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

Échantillon complet

N'oubliez pas de coller ici le code de la solution

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

Résultat

entrez la description de l'image ici

Vasily Bodnarchuk
la source
0

J'ai écrit une extension avec une méthode qui supprime tous les contrôleurs entre root et top, sauf indication contraire.

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

}

Si vous souhaitez utiliser range (par exemple: 2 à 5), vous pouvez simplement utiliser

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

Testé sur iOS 12.2, Swift 5

Adam
la source
0

// suppression des viewcontrollers par noms de classe de la pile puis rejet de la vue actuelle.

 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)
Mirza Q Ali
la source