탐색 스택에서 뷰 컨트롤러 제거


92

5 개의 UIViewController가있는 탐색 스택이 있습니다. 다섯 번째 뷰 컨트롤러에서 버튼을 클릭하여 스택에서 세 번째 및 네 번째 뷰 컨트롤러를 제거하고 싶습니다. 이것이 가능합니까? 그렇다면 어떻게?

답변:


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

나는 이것을 묶었 고 작동하지 않습니다. 나는 속성과 관련된 것이 뷰 컨트롤러를 할당 해제하지 않도록 만든다고 들었습니다.
Noah Passalacqua

1
이것은 iOS <7에서 작동했지만 iOS 7에서는 이상하게 작동합니다.
Ben H

1
iOS 8에서 잘 작동합니다!
Evan R

4
Vivek : 당신이 시도한 것을 보여주고 반대 투표 전에 생각할 예의가 있습니다.
Nitin 2015 년

7
이 메서드는 실제로 스택에서 viewcontroller를 제거하지만 영향을받지 않는 navigationitems 스택도있는 것 같습니다. ios 8.4에서 얻는 동작은 다음과 같습니다. 컨트롤러 1 2 3 4 5가 있다고 가정합니다. 4를 제거하면 5에 표시된 뒤로 버튼은 영향을받지 않습니다. 다시 클릭하면 3이 표시되지만 제목은 4입니다. 다시 클릭하면 제목이 3 인 3이 표시됩니다.
Radu Simionescu 2015 년

49

먼저 배열의 모든 뷰 컨트롤러를 가져온 다음 해당 뷰 컨트롤러 클래스를 확인한 후 원하는 컨트롤러를 삭제할 수 있습니다.

다음은 작은 코드입니다.

NSArray* tempVCA = [self.navigationController viewControllers];

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

나는 이것이 당신의 일을 더 쉽게 만들 것이라고 생각합니다.


이것은 다목적으로 사용할 수 있습니다. 감사합니다 :)
Hemang 2013-08-21

10
이것을 사용하면 컨트롤러가 제대로 제거됩니다. 그러나 "뒤로"버튼을 사용하면 내 탐색 모음에 제거 된 viewController의 정보가 표시됩니다. 다른 사람이이 이상한 행동을 받고 어떻게 해결할 수 있습니까?
로빈 Ellerkmann

1
@Robin Ellerkmann은 그 문제에 대한 해결책을 찾았습니까? viewcontroller를 제거하고 있지만 뒤로 버튼이 탐색 모음에 남아 있습니다.
Mehmet Emre 2016

2
@MehmetEmre self.navigationController? .viewControllers.removeLast ()와 함께 Swift 2.1을 사용합니다. 이것은 나를 위해 꽤 잘 작동합니다.
Robin Ellerkmann

1
내가 4 viewcontroller 메모리에있을 때 로그 아웃하면 모든 viewcontroller가 제거되면 80MB였습니다. 메모리는 여전히 80MB입니다. 따라서 메모리가 해제되지 않습니다. :(
Anil Gupta

39

Swift 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, range 등과 같은 더 많은 가능한 작업이 있습니다.


3
귀하의 답변을 살펴보면 내 프로젝트의 워크 플로에 대한 아이디어가 생겼습니다. 감사합니다.
Anirudha Mahale

이것은 NavigationController 자체를 제거하고 뷰 컨트롤러 스택을 정리하지 않습니다.
Daniel Beltrami 2018 년

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)한 줄로 작업 할 것입니다. :-)
Mark

10

setViewControllersfrom 함수를 사용 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];. 그것은 이상한 행동을 만들고, @ robin-ellerkmann이보고 한 것처럼 팝업 될 때 여전히 표시되는 ViewController 탐색을 제거했습니다.


5
이것은 실제로 최상의 솔루션입니다. navigationController? .viewControllers 배열에서 VC를 제거하고 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
당신이 탐색 컨트롤러를 풀기 강제하지 않을 그래서, 당신은 할 수 그것에게 if 문if var navArray = ... { ... }
카일리

6

Swift 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 번째보기 컨트롤러에서 2 번째보기 컨트롤러로 이동하려는 경우 (3 번째 및 4 번째 건너 뛰기)을 사용하고 싶습니다 [self.navigationController popToviewController:secondViewController].

secondViewController탐색 컨트롤러 스택에서 얻을 수 있습니다 .

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

1
현재 뷰 컨트롤러를 팝하고 싶지 않습니다. 현재 뷰 컨트롤러는 그대로 유지되어야합니다. 하지만 2 viewcontrollers 스택에서 그 아래에 누워있는 팝업 필요
장 폴 스콧

트윗 담아 가기 팝업이 아니라면 왜 그렇게 하시겠습니까?!.
Vignesh 2012

동일한 뷰 컨트롤러의 다른 인스턴스가 스택으로 푸시되는 경우가 있습니다. 따라서 새 인스턴스가 생성되어 스택에 푸시되면 이전 인스턴스와 이와 관련된 뷰 컨트롤러를 표시하고 싶습니다.
Jean Paul Scott

때문에 '팝업 슬쩍'제스처의 아이폰 OS 7 필요에 따라이 것없는 일을 @Vignesh
데니스 Pashkov

@JeanPaulScott은 원하는 것을 달성하기 위해 새 뷰 컨트롤러 인스턴스를 푸시하기 전에 두 번 팝업하는 것입니다.
Radu Simionescu 2015 년

4

이것을 사용하십시오

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

navigationController의 ViewController를 처리합니다. viewControllers 및 navigationBar에 쌓인 navigationItems.

참고 : 최소한 viewDidAppear 이후에 호출해야합니다.


1
이 방법은 Swift 5, Xcode 10.3에서 완벽하게 작동했습니다 ... if let navVCsCount = navigationController? .viewControllers.count {self.navigationController? .viewControllers.removeSubrange (navVCsCount-3 .. <navVCsCount-1)}
Kedar Sukerkar

2

이 솔루션은 빠른 4에서 저에게 효과적이었습니다.

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

스택의 현재 뷰 컨트롤러 인덱스는 다음과 같습니다.

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

이전 컨트롤러의 viewDidDisappear () 또는 새 컨트롤러의 viewDidAppear () 후에이 유틸리티 함수를 호출해야합니다.


1

세부

  • Swift 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

달리 지정하지 않는 한 루트와 상단 사이의 모든 컨트롤러를 제거하는 메서드로 확장을 작성했습니다.

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)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.