Viewcontroller aus dem Navigationsstapel entfernen

92

Ich habe einen Navigationsstapel mit beispielsweise 5 UIViewControllern. Ich möchte den 3. und 4. Viewcontroller im Stapel auf Knopfdruck im 5. Viewcontroller entfernen. Ist das möglich? Wenn das so ist, wie?

Jean Paul Scott
quelle

Antworten:

166

Verwenden Sie diesen Code und genießen Sie:

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

Hoffe das wird dir helfen.

Bearbeiten: 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
Nitin
quelle
Ich habe dies gebunden und funktioniert nicht. Mir wurde gesagt, dass etwas, das mit den Eigenschaften zu tun hat, dazu führt, dass die Viewcontroller nicht freigegeben werden.
Noah Passalacqua
1
Dies funktionierte in iOS <7, führt aber zu einem seltsamen Verhalten in iOS 7.
Ben H
1
Funktioniert hervorragend für iOS 8!
Evan R
4
Vivek: Zeigen Sie mir, was Sie versucht haben, und denken Sie höflich nach, bevor Sie negativ abstimmen.
Nitin
7
Diese Methode entfernt zwar einen Viewcontroller aus dem Stapel, aber es scheint auch einen Navigationselementstapel zu geben, der nicht betroffen ist. Das Verhalten, das ich in ios 8.4 bekomme, ist wie folgt: Angenommen, wir haben Controller 1 2 3 4 5. Ich entferne 4, die auf 5 angezeigte Zurück-Schaltfläche ist nicht betroffen. Ich klicke zurück, es zeigt 3, aber den Titel 4. Ich klicke wieder zurück, es zeigt 3 mit dem Titel 3
Radu Simionescu
48

Sie können zuerst alle View-Controller im Array abrufen und dann nach Überprüfung mit der entsprechenden View-Controller-Klasse die gewünschte löschen.

Hier ist ein kleiner Code:

NSArray* tempVCA = [self.navigationController viewControllers];

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

Ich denke, das wird Ihre Arbeit erleichtern.

Sourabh Bhardwaj
quelle
Dieser kann für mehrere Zwecke verwendet werden. Danke :)
Hemang
10
Wenn ich dies benutze, wird der Controller ordnungsgemäß entfernt. Wenn ich jedoch die Schaltfläche "Zurück" verwende, werden in meiner Navigationsleiste die Informationen des entfernten viewControllers angezeigt. Erhält jemand anderes dieses seltsame Verhalten und wie kann ich es beheben?
Robin Ellerkmann
1
@Robin Ellerkmann hast du eine Lösung für dieses Problem gefunden? Ich entferne den Viewcontroller, aber die Zurück-Schaltfläche bleibt in der Navigationsleiste.
Mehmet Emre
2
@MehmetEmre Ich verwende Swift 2.1 mit self.navigationController? .ViewControllers.removeLast (). Das funktioniert ziemlich gut für mich.
Robin Ellerkmann
1
Als ich in 4 Viewcontroller war, war der Speicher 80 MB groß, als ich mich abmeldete. Alle Viewcontroller wurden entfernt. Speicher noch 80MB. Der Speicher wird also nicht freigegeben. :(
Anil Gupta
39

Swift 3 & 4/5

self.navigationController!.viewControllers.removeAll()

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

Swift 2.1

alles entfernen:

self.navigationController!.viewControllers.removeAll()

am Index entfernen

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

Es gibt eine Reihe weiterer möglicher Aktionen wie removeFirst, range usw.

Kuzdu
quelle
3
Als ich Ihre Antwort betrachtete, bekam ich eine Idee für den Workflow meines Projekts. Vielen Dank.
Anirudha Mahale
Dadurch wird der NavigationController selbst entfernt und kein Stapel von View-Controllern gereinigt
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
quelle
3
return !vc.isKind(of: MyViewController.self) && !vc.isKind(of: MyViewController2.self)würde den Job in einer Zeile machen :-)
Mark
10

Die Verwendung der setViewControllersFunktion von UINavigationControllerist der beste Weg. Es gibt auch animatedParameter zum Aktivieren der Animation.

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

Beispiel in schneller Frage

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

Ich habe es auf andere Weise versucht [tempVC removeFromParentViewController];. Es macht seltsames Verhalten, entfernte ViewController-Navigation wird immer noch angezeigt, wenn Popback wie von @ robin-ellerkmann gemeldet

Thein
quelle
5
Dies ist tatsächlich die beste Lösung: Entfernen des VC aus dem Array navigationController? .CviewControllers und Verwenden von setViewControllers zum Zuweisen des neuen Arrays. Ich habe auch nach Zombies oder Referenzzyklen gesucht, es ist sicher.
OhadM
Ich bestätige, dass dies eine hervorragende Lösung ist: Ich verwende diese setViewControllers(_:animated:)Technik tatsächlich auf beide Arten: um mehrere Controller zu platzieren und um mehrere Controller zu pushen.
Cœur
8

Swift 2.0:

  var navArray:Array = (self.navigationController?.viewControllers)!
  navArray.removeAtIndex(navArray.count-2)
  self.navigationController?.viewControllers = navArray
tahir raees
quelle
2
Damit Sie den Navigationscontroller nicht zwangsweise auspacken müssen, können Sie eine if-Anweisung if var navArray = ... { ... }
festlegen
6

Swift 5, Xcode 11.3

Ich fand diesen Ansatz einfach, indem ich angab, welche View-Controller Sie aus dem Navigationsstapel entfernen möchten.

extension UINavigationController {

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

Anwendungsbeispiel:

navigationController.removeViewController(YourViewController.self)
Mitchell C.
quelle
5

Wenn Sie versuchen, vom 5. Ansichts-Controller zum 2. Ansichts-Controller zu wechseln (3. und 4. überspringen), möchten Sie verwenden [self.navigationController popToviewController:secondViewController] .

Sie können die secondViewControllervom Navigations-Controller-Stack erhalten.

secondViewController =  [self.navigationController.viewControllers objectAtIndex:yourViewControllerIndex];
Vignesh
quelle
1
Ich möchte den aktuellen Viewcontroller nicht öffnen. Der aktuelle Viewcontroller sollte intakt bleiben. Aber ich muss die 2 darunter liegenden Viewcontroller in den Stapel legen
Jean Paul Scott
@ JeanPaulScott. Ich frage mich, warum du das tun willst, wenn du nicht auftauchst?!.
Vignesh
Es gibt einen Fall, in dem verschiedene Instanzen desselben Viewcontrollers in den Stapel geschoben werden. Wenn also eine neue Instanz erstellt und in den Stapel verschoben wird, möchte ich die vorherige Instanz und den damit verbundenen Viewcontroller herausspringen lassen.
Jean Paul Scott
@Vignesh Dies würde in iOS 7 aufgrund der Geste "Swipe to Pop"
Dennis
@JeanPaulScott Um das zu erreichen, was Sie wollen, ist es am sichersten, zweimal zu platzen, bevor Sie Ihre neue View Controller-Instanz verschieben.
Radu Simionescu
4

Benutze das

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

Es kümmert sich um ViewController von NavigationController. viewController und auch ein Navigationselement, das in der Navigationsleiste gestapelt ist.

Hinweis: Rufen Sie es mindestens nach viewDidAppear auf

Nikola Markovic
quelle
1
Diese Methode funktionierte perfekt für mich in Swift 5, Xcode 10.3 ... wenn navVCsCount = navigationController? .ViewControllers.count {self.navigationController? .ViewControllers.removeSubrange (navVCsCount-3 .. <navVCsCount - 1)}
Kedar Sukerkar
2

Diese Lösung funktionierte für mich in schnell 4:

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

Ihr aktueller View Controller-Index im Stapel lautet:

self.navigationController!.viewControllers.count - 1
Babak
quelle
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)
}}

Stellen Sie sicher, dass Sie diese Dienstprogrammfunktion nach viewDidDisappear () des vorherigen Controllers oder viewDidAppear () des neuen Controllers aufrufen

Kedar Sukerkar
quelle
1

Einzelheiten

  • Swift 5.1, Xcode 11.3.1

Lösung

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

Verwendung

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 }

Vollständige Probe

Vergessen Sie nicht, hier den Lösungscode einzufügen

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

Ergebnis

Geben Sie hier die Bildbeschreibung ein

Wassili Bodnarchuk
quelle
0

Ich habe eine Erweiterung mit Methode geschrieben, die alle Controller zwischen root und top entfernt, sofern nicht anders angegeben.

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

}}

Wenn Sie den Bereich verwenden möchten (zum Beispiel: 2 bis 5), können Sie ihn einfach verwenden

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

Getestet auf iOS 12.2, Swift 5

Adam
quelle
0

// Entfernen der Viewcontroller nach Klassennamen vom Stack und anschließendes Schließen der aktuellen Ansicht.

 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
quelle