Rimozione dei viewcontroller dallo stack di navigazione


Risposte:


167

Usa questo codice e divertiti:

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

Spero che questo ti possa aiutare.

Modifica: codice 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

l'ho legato e non funziona. mi è stato detto che qualcosa a che fare con le proprietà sta causando la mancata deallocazione dei viewcontroller.
Noah Passalacqua

1
questo ha funzionato in iOS <7, ma provoca uno strano comportamento in iOS 7.
Ben H

1
Funziona alla grande per iOS 8!
Evan R

4
Vivek: Mostrami cosa hai provato e abbi la cortesia di pensare prima del voto negativo.
Nitin

7
questo metodo rimuove effettivamente un viewcontroller dallo stack, ma sembra esserci anche uno stack di elementi di navigazione che non viene influenzato. Il comportamento che ottengo in ios 8.4 è questo: diciamo che abbiamo i controller 1 2 3 4 5. Rimuovo 4, il pulsante indietro mostrato su 5 non è influenzato. Faccio clic indietro, mostra 3 ma il titolo di 4. Faccio clic di nuovo, mostra 3 con il titolo di 3
Radu Simionescu

49

È possibile prima ottenere tutti i controller di visualizzazione nell'array e quindi, dopo aver verificato con la classe del controller di visualizzazione corrispondente, è possibile eliminare quello desiderato.

Ecco un piccolo pezzo di codice:

NSArray* tempVCA = [self.navigationController viewControllers];

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

Penso che questo renderà il tuo lavoro più facile.


Questo può essere utilizzato per più scopi. Grazie :)
Hemang

10
Quando lo uso, il controller viene rimosso correttamente. Ma quando uso il pulsante "Indietro" la mia barra di navigazione mostra le informazioni del viewController rimosso. Qualcun altro riceve questo strano comportamento e come posso risolverlo?
Robin Ellerkmann

1
@Robin Ellerkmann hai trovato la soluzione per questo problema? sto rimuovendo il viewcontroller ma il pulsante Indietro rimane nella barra di navigazione.
Mehmet Emre

2
@MehmetEmre Uso Swift 2.1 con self.navigationController? .ViewControllers.removeLast (). Questo funziona abbastanza bene per me.
Robin Ellerkmann

1
Quando ero in 4 viewcontroller la memoria era di 80 MB quando disconnettevo tutti i viewcontroller vengono rimossi. Memoria ancora 80 MB. Quindi la memoria non si sta liberando. :(
Anil Gupta

39

Swift 3 e 4/5

self.navigationController!.viewControllers.removeAll()

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

Swift 2.1

Rimuovi tutto:

self.navigationController!.viewControllers.removeAll()

rimuovere all'indice

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

Ci sono molte più azioni possibili come removeFirst, range ecc.


3
Guardando la tua risposta, ho avuto un'idea per il flusso di lavoro del mio progetto. Molte grazie.
Anirudha Mahale

Questo rimuove il NavigationController da solo, non pulisce una pila di controller di visualizzazione
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
    }
})

3
return !vc.isKind(of: MyViewController.self) && !vc.isKind(of: MyViewController2.self)farebbe il lavoro in una riga :-)
Mark

10

Usare la setViewControllersfunzione da UINavigationControllerè il modo migliore. C'è anche un animatedparametro per abilitare l'animazione.

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

Esempio in rapido per domanda

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

Ho provato altri modi come [tempVC removeFromParentViewController];. Rende un comportamento strano, la navigazione ViewController rimossa viene ancora mostrata quando torna indietro come riportato da @ robin-ellerkmann


5
Questa è effettivamente la soluzione migliore: rimuovere il VC dall'array navigationController? .ViewControllers e usare setViewControllers per assegnare il nuovo array. Ho anche verificato la presenza di zombi o cicli di riferimento, è sicuro.
OhadM

Confermo che è un'ottima soluzione: in realtà sto usando quella setViewControllers(_:animated:)tecnica in entrambi i modi: per far apparire più controller e per spingere più controller.
Cœur

8

Swift 2.0:

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

2
Quindi non stai forzando a scartare il controller di navigazione, potresti renderlo un'istruzione ifif var navArray = ... { ... }
Kiley

6

Swift 5, Xcode 11.3

Ho trovato questo approccio semplice specificando quali controller di visualizzazione si desidera rimuovere dallo stack di navigazione.

extension UINavigationController {

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

Esempio di utilizzo:

navigationController.removeViewController(YourViewController.self)

5

Se stai tentando di passare al 2 ° controller di visualizzazione dal 5 ° controller di visualizzazione (saltando il 3 ° e il 4 °), ti piacerebbe usare [self.navigationController popToviewController:secondViewController].

È possibile ottenerlo secondViewControllerdallo stack del controller di navigazione.

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

1
Non voglio far apparire il viewcontroller corrente. Il viewcontroller corrente dovrebbe rimanere intatto. Ma ho bisogno di far apparire i 2 viewcontroller che si trovano sotto di esso nello stack
Jean Paul Scott

@JeanPaulScott. Mi chiedo perché dovresti farlo, se non per spuntare fuori?!.
Vignesh

C'è un caso in cui avrei diverse istanze dello stesso viewcontroller che vengono inserite nello stack. Quindi, quando una nuova istanza viene creata e inserita nello stack, voglio estrarre l'istanza precedente e il viewcontroller ad essa associato.
Jean Paul Scott

@Vignesh Questo non funzionerebbe come richiesto in iOS 7 a causa del gesto 'swipe to pop'
Dennis Pashkov

@JeanPaulScott per ottenere ciò che desideri, la cosa più sicura è fare clic due volte prima di eseguire il push della nuova istanza del controller di visualizzazione.
Radu Simionescu

4

Usa questo

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

Si prenderà cura dei ViewController di navigationController. viewControllers e anche un navigationItems impilato in navigationBar.

Nota: assicurati di chiamarlo almeno dopo viewDidAppear


1
Questo metodo ha funzionato perfettamente per me in Swift 5, Xcode 10.3 ... if let navVCsCount = navigationController? .ViewControllers.count {self.navigationController? .ViewControllers.removeSubrange (navVCsCount-3 .. <navVCsCount - 1)}
Kedar Sukerkar

2

Questa soluzione ha funzionato per me in swift 4:

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

l'indice del tuo controller di visualizzazione corrente nello stack è:

self.navigationController!.viewControllers.count - 1

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

Assicurati di chiamare questa funzione di utilità dopo viewDidDisappear () del controller precedente o viewDidAppear () del nuovo controller


1

Dettagli

  • Swift 5.1, Xcode 11.3.1

Soluzione

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

Utilizzo

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 }

Campione completo

Non dimenticare di incollare qui il codice della soluzione

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

Risultato

inserisci qui la descrizione dell'immagine


0

Ho scritto un'estensione con metodo che rimuove tutti i controller tra root e top, se non diversamente specificato.

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

}

Se vuoi usare l'intervallo (ad esempio: da 2 a 5) puoi semplicemente usare

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

Testato su iOS 12.2, Swift 5


0

// rimuove i viewcontroller in base ai nomi delle classi dallo stack e quindi chiude la visualizzazione corrente.

 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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.