Видалення контролерів перегляду зі стеку навігації


92

У мене є стек навігації, скажімо, з 5 UIViewControllers. Я хочу видалити 3-й і 4-й контролери перегляду в стеку, натиснувши кнопку в 5-му контролері перегляду. Чи можна це зробити? Якщо так, то як?

Відповіді:


167

Використовуйте цей код і насолоджуйтесь:

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

Сподіваюся, це допоможе вам.

Редагувати: швидкий код

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

я прив'язав це і не працює. мені сказали, що щось спільне з властивостями призводить до того, що він не звільняє контролери перегляду.
Ной Пассалаква

1
це працювало в iOS <7, але призводить до дивної поведінки в iOS 7.
Ben H

1
Чудово працює для iOS 8!
Еван Р

4
Вівек: Покажи мені, що ти спробував, і будь ввічливим, щоб подумати перед негативним голосуванням.
Нітін,

7
цей метод дійсно видаляє контролер вигляду зі стеку, але, здається, існує стек елементів навігації, який не постраждає. Поведінка, яку я отримую в iOS 8.4, така: скажімо, у нас є контролери 1 2 3 4 5. Я видаляю 4, кнопка "Назад", показана на 5, не впливає. Я натискаю назад, це показує 3, але заголовок 4. Я знову натискаю назад, він показує 3 із заголовком 3
Раду Сіміонеску

49

Ви можете спочатку отримати всі контролери перегляду в масиві, а потім, перевіривши відповідний клас контролера перегляду, ви можете видалити той, який хочете.

Ось невеличка частина коду:

NSArray* tempVCA = [self.navigationController viewControllers];

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

Думаю, це полегшить вам роботу.


Цей можна використовувати для багатоцільового використання. Дякую :)
Hemang

10
Коли я цим користуюся, контролер видаляється належним чином. Але коли я використовую кнопку "Назад", на моїй панелі навігації відображається інформація про видалений viewController. Хтось ще сприймає цю дивну поведінку і як я можу це виправити?
Робін Еллеркманн,

1
@Robin Ellerkmann Ви знайшли рішення цієї проблеми? Я видаляю контролер перегляду, але кнопка "Назад" залишається на панелі навігації.
Мехмет Емре

2
@MehmetEmre Я використовую Swift 2.1 з self.navigationController? .ViewControllers.removeLast (). Для мене це працює досить добре.
Робін Еллеркманн,

1
Коли я знаходився в 4 пам'яті контролера перегляду, було 80 Мб пам’яті, коли виходили з системи, усі контролери перегляду видалялися. Пам'ять все ще 80 Мб. Отже, пам’ять не звільняється. :(
Аніл Гупта,

39

Свіфт 3 і 4/5

self.navigationController!.viewControllers.removeAll()

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

Свіфт 2.1

видалити всі:

self.navigationController!.viewControllers.removeAll()

видалити за індексом

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

Є ще купа можливих дій, таких як removeFirst, діапазон тощо.


3
Переглядаючи вашу відповідь, я отримав уявлення про робочий процес свого проекту. Дуже дякую.
Анірудха Махале

Це видалить NavigationController самостійно, а не очистить стос контролерів огляду
Daniel Beltrami

16

Свіфт 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)зробив би роботу в один рядок :-)
Марк

10

Використання setViewControllersфункції from UINavigationController- найкращий спосіб. Також є animatedпараметр для ввімкнення анімації.

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

Приклад в швидкому порядку для запитання

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

Я спробував інші способи, як [tempVC removeFromParentViewController];. Це робить дивну поведінку, вилучена навігація ViewController все ще відображається при поверненні назад, як повідомляє @ robin-ellerkmann


5
Це насправді найкраще рішення: видалення VC з масиву navigationController? .ViewControllers та використання setViewControllers для призначення нового масиву. Я також перевірив наявність зомбі чи контрольних циклів, це безпечно.
OhadM

Я підтверджую, що це відмінне рішення: я насправді використовую цю setViewControllers(_:animated:)техніку в обох напрямках: підключити кілька контролерів і натиснути на кілька контролерів.
Cœur

8

Свіфт 2.0:

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

2
Отже, ви не змушуєте розгортати навігаційний контролер, ви можете зробити це заявою ifif var navArray = ... { ... }
Кайлі

6

Свіфт 5, Xcode 11.3

Я знайшов цей підхід простим, вказавши, який контролер (и) подання ви хочете видалити зі стеку навігації.

extension UINavigationController {

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

Приклад використання:

navigationController.removeViewController(YourViewController.self)

5

Якщо ви намагаєтесь перейти до другого контролера перегляду з 5-го контролера перегляду (пропускаючи 3-й та 4-й), ви хотіли б скористатися цим [self.navigationController popToviewController:secondViewController] .

Ви можете отримати його secondViewControllerз стеку навігаційного контролера.

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

1
Не хочу виводити поточний контролер подання. Поточний контролер перегляду повинен залишатися цілим. Але мені потрібно вставити 2 контролери перегляду, що лежать під ним у стосі
Jean Paul Scott

@JeanPaulScott. Цікаво, чому ти хотів би це зробити, якби не спливаюче?!.
Vignesh

Є випадок, коли я мав би різні випадки, коли один і той самий контролер перегляду був засунутий у стек. Отже, коли новий екземпляр створюється та вставляється в стек, я хочу висунути попередній екземпляр та контролер перегляду, пов’язаний із цим.
Jean Paul Scott

@Vignesh Це не спрацює, як це вимагається в iOS 7, через жест `` проведіть пальцем ''
Денніс Пашков,

@JeanPaulScott, щоб досягти того, що ти хочеш, найбезпечніше - це двічі вискочити, перш ніж натискати новий екземпляр контролера перегляду.
Раду Сіміонеску

4

Використовуй це

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

Він подбає про ViewControllers навігаційного контролера. viewControllers, а також елементи навігації, розміщені в навігаційній панелі.

Примітка: Обов’язково зателефонуйте йому принаймні після viewDidAppear


1
Цей метод відмінно спрацював для мене в Swift 5, Xcode 10.3 ... якщо дозволити navVCsCount = navigationController? .ViewControllers.count {self.navigationController? .ViewControllers.removeSubrange (navVCsCount-3 .. <navVCsCount - 1)}
Кедар Сукеркар

2

Це рішення спрацювало для мене в швидкому 4:

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

Ваш поточний індекс контролера перегляду в стеку:

self.navigationController!.viewControllers.count - 1

2

Свіфт 5.1, Xcode 11

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

Обов’язково викликайте цю функцію утиліти після viewDidDisappear () попереднього контролера або viewDidAppear () нового контролера


1

Деталі

  • Свіфт 5.1, Xcode 11.3.1

Рішення

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

Використання

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 }

Повний зразок

Не забудьте вставити сюди код рішення

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

Результат

введіть тут опис зображення


0

Я написав розширення з методом, який видаляє всі контролери між root і top, якщо не вказано інше.

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

}

Якщо ви хочете використовувати діапазон (наприклад: від 2 до 5), ви можете просто використовувати

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

Перевірено на iOS 12.2, Swift 5


0

// видалення контролерів перегляду за іменами класів зі стеку, а потім відхилення поточного подання.

 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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.