Menghapus pengontrol tampilan dari tumpukan navigasi

96

Saya memiliki tumpukan navigasi, dengan katakanlah 5 UIViewControllers. Saya ingin menghapus viewcontroller ke-3 dan ke-4 di tumpukan dengan mengklik tombol di viewcontroller ke-5. Apakah mungkin melakukan ini? Jika ya, bagaimana caranya?

Jean Paul Scott
sumber

Jawaban:

171

Gunakan kode ini dan nikmati:

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

Semoga ini bisa membantu Anda.

Edit: Kode 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
Nitin
sumber
saya telah mengikat ini dan tidak bekerja. Saya diberi tahu bahwa ada hubungannya dengan properti yang menyebabkannya tidak dapat membatalkan alokasi pengontrol tampilan.
Noah Passalacqua
1
ini bekerja di iOS <7, tetapi menghasilkan perilaku aneh di iOS 7.
Ben H
1
Berfungsi bagus untuk iOS 8!
Evan R
4
Vivek: Tunjukkan apa yang telah Anda coba dan pikirkan sopan santun sebelum memberikan suara negatif.
Nitin
7
metode ini memang menghapus viewcontroller dari tumpukan tetapi juga tampaknya ada tumpukan item navigasi yang tidak terpengaruh. Perilaku yang saya dapatkan di ios 8.4 adalah seperti ini: katakanlah kita memiliki pengontrol 1 2 3 4 5. Saya menghapus 4, tombol kembali yang ditunjukkan pada 5 tidak terpengaruh. Saya klik kembali, itu menunjukkan 3 tetapi judul 4. Saya klik kembali, itu menunjukkan 3 dengan judul 3
Radu Simionescu
49

Pertama-tama Anda bisa mendapatkan semua pengontrol tampilan dalam larik dan kemudian setelah memeriksa dengan kelas pengontrol tampilan yang sesuai, Anda dapat menghapus yang Anda inginkan.

Berikut adalah potongan kecil kode:

NSArray* tempVCA = [self.navigationController viewControllers];

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

Saya pikir ini akan membuat pekerjaan Anda lebih mudah.

Sourabh Bhardwaj
sumber
Yang satu ini bisa digunakan untuk banyak tujuan. Terima kasih :)
Hemang
10
Ketika saya menggunakan ini, pengontrol dilepas dengan benar. Tetapi ketika saya menggunakan tombol "Kembali", bilah navigasi saya menampilkan informasi viewController yang dihapus. Apakah ada orang lain yang menerima perilaku aneh ini dan bagaimana cara memperbaikinya?
Robin Ellerkmann
1
@Robin Ellerkmann apakah Anda menemukan solusi untuk masalah itu? saya menghapus viewcontroller tetapi tombol kembali tetap di bilah navigasi.
Mehmet Emre
2
@MehmetEmre Saya menggunakan Swift 2.1 dengan self.navigationController? .ViewControllers.removeLast (). Ini bekerja cukup baik untuk saya.
Robin Ellerkmann
1
Ketika saya berada di 4 memori viewcontroller adalah 80MB ketika log out semua viewcontroller dihapus. Memori masih 80MB. Jadi memori tidak dilepaskan. :(
Anil Gupta
40

Cepat 3 & 4/5

self.navigationController!.viewControllers.removeAll()

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

Swift 2.1.0

menghapus semua:

self.navigationController!.viewControllers.removeAll()

hapus di indeks

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

Ada banyak tindakan yang lebih mungkin seperti removeFirst, range, dll.

kuzdu
sumber
3
Melihat jawaban Anda, saya mendapat ide untuk alur kerja proyek saya. Terima kasih banyak.
Anirudha Mahale
Ini menghapus NavigationController itu sendiri, bukan membersihkan tumpukan pengontrol tampilan
Daniel Beltrami
16

Cepat 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
sumber
3
return !vc.isKind(of: MyViewController.self) && !vc.isKind(of: MyViewController2.self)akan melakukan pekerjaan dalam satu baris :-)
Tandai
10

Menggunakan setViewControllersfungsi dari UINavigationControlleradalah cara terbaik. Ada juga animatedparameter untuk mengaktifkan animasi.

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

Contoh cepat untuk pertanyaan

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

Saya mencoba cara lain seperti [tempVC removeFromParentViewController];. Itu membuat perilaku aneh, navigasi ViewController yang dihapus masih muncul ketika pop back seperti dilaporkan oleh @ robin-ellerkmann

Di
sumber
5
Ini sebenarnya adalah solusi terbaik: menghapus VC dari larik navigationController? .ViewControllers dan menggunakan setViewControllers untuk menetapkan larik baru. Saya juga telah memeriksa zombie atau siklus referensi, aman.
OhadM
Saya mengonfirmasi bahwa ini adalah solusi yang sangat baik: Saya sebenarnya menggunakan setViewControllers(_:animated:)teknik itu dengan dua cara: untuk memunculkan beberapa pengontrol dan untuk mendorong banyak pengontrol.
Cœur
8

Swift 2.0:

  var navArray:Array = (self.navigationController?.viewControllers)!
  navArray.removeAtIndex(navArray.count-2)
  self.navigationController?.viewControllers = navArray
tahir raees
sumber
2
Jadi Anda tidak memaksa membuka pengontrol navigasi, Anda dapat membuatnya menjadi pernyataan ifif var navArray = ... { ... }
Kiley
8

Swift 5, Xcode 11.3

Saya menemukan pendekatan ini sederhana dengan menentukan pengontrol tampilan mana yang ingin Anda hapus dari tumpukan navigasi.

extension UINavigationController {

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

Contoh penggunaan:

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

Jika Anda mencoba untuk pindah ke pengontrol tampilan ke-2 dari pengontrol tampilan ke-5 (melompati pengontrol tampilan ke-3 dan ke-4), Anda ingin menggunakan [self.navigationController popToviewController:secondViewController].

Anda dapat memperolehnya secondViewControllerdari tumpukan pengontrol navigasi.

secondViewController =  [self.navigationController.viewControllers objectAtIndex:yourViewControllerIndex];
Vignesh
sumber
1
Tidak ingin memunculkan viewcontroller saat ini. Viewcontroller saat ini harus tetap utuh. Tapi saya perlu memunculkan 2 viewcontrollers yang tergeletak di bawah tumpukan
Jean Paul Scott
@Bayu_joo Saya bertanya-tanya Mengapa Anda ingin melakukan itu, jika bukan karena bermunculan?!.
Vignesh
Ada kasus di mana saya akan memiliki contoh berbeda dari viewcontroller yang sama didorong ke tumpukan. Jadi ketika sebuah instance baru dibuat dan didorong ke dalam stack, saya ingin mengeluarkan instance sebelumnya dan viewcontroller yang terkait dengannya.
Jean Paul Scott
@Vignesh Ini tidak akan berfungsi seperti yang diperlukan di iOS 7 karena gerakan 'geser ke pop'
Dennis Pashkov
@JeanPaulScott untuk mencapai apa yang Anda inginkan, hal teraman adalah memunculkan dua kali sebelum mendorong instance pengontrol tampilan baru Anda.
Radu Simionescu
4

Gunakan ini

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

Ini akan menangani ViewControllers dari navigationController. viewControllers dan juga navigationItems yang ditumpuk di navigationBar.

Catatan: Pastikan untuk memanggilnya setidaknya setelah viewDidAppear

Nikola Markovic
sumber
1
Metode ini bekerja dengan sempurna untuk saya di Swift 5, Xcode 10.3 ... if let navVCsCount = navigationController? .ViewControllers.count {self.navigationController? .ViewControllers.removeSubrange (navVCsCount-3 .. <navVCsCount - 1)}
Kedar Sukerkar
3

Swift 5.1, Xcode 11

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

Pastikan untuk memanggil fungsi utilitas ini setelah viewDidDisappear () dari pengontrol sebelumnya atau viewDidAppear () pengontrol baru

Kedar Sukerkar
sumber
3

Detail

  • Swift 5.1, Xcode 11.3.1

Larutan

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

Pemakaian

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 }

Sampel Lengkap

Jangan lupa paste disini kode solusinya

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

Hasil

masukkan deskripsi gambar di sini

Bodnarchuk dengan mudah
sumber
2

Solusi ini berhasil untuk saya dalam 4 cepat:

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

indeks pengontrol tampilan Anda saat ini dalam tumpukan adalah:

self.navigationController!.viewControllers.count - 1
babak
sumber
0

Saya menulis ekstensi dengan metode yang menghapus semua pengontrol antara root dan top, kecuali ditentukan lain.

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

}

Kalau mau pakai range (misal: 2 sampai 5) tinggal pakai aja

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

Diuji di iOS 12.2, Swift 5

Adam
sumber
0

// menghapus viewcontrollers dengan nama kelas dari tumpukan dan kemudian menutup tampilan saat ini.

 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
sumber