Eliminando controladores de vista de la pila de navegación


92

Tengo una pila de navegación, con 5 UIViewControllers. Quiero eliminar los controladores de vista tercero y cuarto de la pila con solo hacer clic en un botón en el controlador de vista quinto. ¿Es posible hacer esto? ¿Si es así, cómo?

Respuestas:


167

Usa este código y disfruta:

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

Espero que esto te ayudará.

Editar: Código 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

He atado esto y no funciona. Me dijeron que algo relacionado con las propiedades estaba provocando que no desasignaran los controladores de vista.
Noah Passalacqua

1
esto funcionó en iOS <7, pero resulta en un comportamiento extraño en iOS 7.
Ben H

1
¡Funciona muy bien para iOS 8!
Evan R

4
Vivek: Muéstreme lo que ha intentado y tenga la cortesía de pensar antes del voto negativo.
Nitin

7
este método elimina un controlador de vista de la pila, pero también parece haber una pila de elementos de navegación que no se ve afectada. El comportamiento que obtengo en ios 8.4 es así: digamos que tenemos controladores 1 2 3 4 5. Elimino 4, el botón de retroceso que se muestra en 5 no se ve afectado. Hago clic atrás, muestra 3 pero el título de 4. Hago clic de nuevo, muestra 3 con el título de 3
Radu Simionescu

49

Primero puede obtener todos los controladores de vista en la matriz y luego, después de verificar con la clase de controlador de vista correspondiente, puede eliminar el que desee.

Aquí hay un pequeño fragmento de código:

NSArray* tempVCA = [self.navigationController viewControllers];

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

Creo que esto facilitará tu trabajo.


Este se puede utilizar para múltiples propósitos. Gracias :)
Hemang

10
Cuando utilizo esto, el controlador se quita correctamente. Pero cuando uso el botón "Atrás", mi barra de navegación muestra la información del viewController eliminado. ¿Alguien más recibe este comportamiento extraño y cómo puedo solucionarlo?
Robin Ellerkmann

1
@Robin Ellerkmann, ¿encontró una solución para ese problema? Estoy eliminando viewcontroller pero el botón de retroceso permanece en la barra de navegación.
Mehmet Emre

2
@MehmetEmre Uso Swift 2.1 con self.navigationController? .ViewControllers.removeLast (). Esto funciona bastante bien para mí.
Robin Ellerkmann

1
Cuando estaba en 4 viewcontroller, la memoria era de 80 MB cuando se desconectaban todos los viewcontroller. La memoria sigue siendo de 80 MB. Entonces la memoria no se libera. :(
Anil Gupta

39

Swift 3 y 4/5

self.navigationController!.viewControllers.removeAll()

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

Swift 2.1

eliminar todo:

self.navigationController!.viewControllers.removeAll()

eliminar en el índice

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

Hay un montón de acciones posibles como removeFirst, range, etc.


3
Al ver su respuesta, se me ocurrió una idea para el flujo de trabajo de mi proyecto. Muchas gracias.
Anirudha Mahale

Esto elimina el NavigationController en sí mismo, no limpia una pila de controladores de vista
Daniel Beltrami

16

Rápido 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)haría el trabajo en una línea :-)
Mark

10

Usar la setViewControllersfunción de UINavigationControlleres la mejor manera. También hay un animatedparámetro para habilitar la animación.

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

Ejemplo en rápido para pregunta

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

Intenté otras formas como [tempVC removeFromParentViewController];. Tiene un comportamiento extraño, eliminó la navegación de ViewController que aún se muestra cuando aparece como lo informó @ robin-ellerkmann


5
Esta es en realidad la mejor solución: eliminar el VC de la matriz navigationController? .ViewControllers y usar setViewControllers para asignar la nueva matriz. También verifiqué zombis o ciclos de referencia, es seguro.
OhadM

Confirmo que es una solución excelente: en realidad estoy usando esa setViewControllers(_:animated:)técnica de ambas maneras: para hacer estallar múltiples controladores y presionar múltiples controladores.
Cœur

8

Swift 2.0:

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

2
Por lo tanto, no está forzando a desenvolver el controlador de navegación, podría convertirlo en una declaración ifif var navArray = ... { ... }
Kiley

6

Swift 5, Xcode 11.3

Encontré este enfoque simple al especificar qué controlador (es) de vista desea eliminar de la pila de navegación.

extension UINavigationController {

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

Ejemplo de uso:

navigationController.removeViewController(YourViewController.self)

5

Si está intentando pasar al controlador de la segunda vista desde el controlador de la quinta vista (omitiendo la tercera y la cuarta), le gustaría usar [self.navigationController popToviewController:secondViewController].

Puede obtener el secondViewControllerde la pila del controlador de navegación.

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

1
No quiero hacer estallar el controlador de vista actual. El controlador de vista actual debe permanecer intacto. Pero necesito hacer estallar los 2 controladores de vista que se encuentran debajo de él en la pila
Jean Paul Scott

@JeanPaulScott. Me pregunto ¿Por qué querrías hacer eso, si no fuera por aparecer?
Vignesh

Hay un caso en el que tendría diferentes instancias del mismo controlador de vista empujado a la pila. Entonces, cuando se crea una nueva instancia y se inserta en la pila, quiero sacar la instancia anterior y el controlador de vista asociado con ella.
Jean Paul Scott

@Vignesh Esto no funcionaría como se requiere en iOS 7 debido al gesto 'deslizar para hacer estallar'
Dennis Pashkov

@JeanPaulScott para lograr lo que desea, lo más seguro es hacer estallar dos veces antes de presionar su nueva instancia de controlador de vista.
Radu Simionescu

4

Utilizar este

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

Se encargará de ViewControllers de navigationController. viewControllers y también un navigationItems apilado en navigationBar.

Nota: asegúrese de llamarlo al menos después de viewDidAppear


1
Este método funcionó perfectamente para mí en Swift 5, Xcode 10.3 ... if let navVCsCount = navigationController? .ViewControllers.count {self.navigationController? .ViewControllers.removeSubrange (navVCsCount-3 .. <navVCsCount - 1)}
Kedar Sukerkar

2

Esta solución funcionó para mí en swift 4:

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

su índice actual del controlador de vista en la pila es:

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

Asegúrese de llamar a esta función de utilidad después de viewDidDisappear () del controlador anterior o viewDidAppear () del nuevo controlador


1

Detalles

  • Swift 5.1, Xcode 11.3.1

Solución

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

Uso

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 }

Muestra completa

No olvides pegar aquí el código de la solución.

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

Resultado

ingrese la descripción de la imagen aquí


0

Escribí una extensión con un método que elimina todos los controladores entre la raíz y la parte superior, a menos que se especifique lo contrario.

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 desea usar el rango (por ejemplo: 2 a 5), ​​puede usar

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

Probado en iOS 12.2, Swift 5


0

// eliminando los controladores de vista por nombres de clase de la pila y luego descartando la vista actual.

 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)
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.