Wykonaj akcję po naciśnięciu przycisku paska wstecz UINavigationController

204

Muszę wykonać akcję (opróżnienie tablicy), gdy UINavigationControllerzostanie wciśnięty przycisk Wstecz a , a przycisk nadal powoduje wyświetlenie poprzedniego ViewControllerna stosie. Jak mogę to zrobić za pomocą szybkiego? wprowadź opis zdjęcia tutaj

StevenR
źródło

Odpowiedzi:

152

Jedną z opcji byłoby wdrożenie własnego niestandardowego przycisku powrotu. Konieczne będzie dodanie następującego kodu do metody viewDidLoad:

- (void) viewDidLoad {
    [super viewDidLoad];
    self.navigationItem.hidesBackButton = YES;
    UIBarButtonItem *newBackButton = [[UIBarButtonItem alloc] initWithTitle:@"Back" style:UIBarButtonItemStyleBordered target:self action:@selector(back:)];
    self.navigationItem.leftBarButtonItem = newBackButton;
}

- (void) back:(UIBarButtonItem *)sender {
    // Perform your custom actions
    // ...
    // Go back to the previous ViewController
    [self.navigationController popViewControllerAnimated:YES];
}

AKTUALIZACJA:

Oto wersja Swift:

    override func viewDidLoad {
        super.viewDidLoad()
        self.navigationItem.hidesBackButton = true
        let newBackButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Bordered, target: self, action: "back:")
        self.navigationItem.leftBarButtonItem = newBackButton
    }

    func back(sender: UIBarButtonItem) {
        // Perform your custom actions
        // ...
        // Go back to the previous ViewController
        self.navigationController?.popViewControllerAnimated(true)
    }

AKTUALIZACJA 2:

Oto wersja Swift 3:

    override func viewDidLoad {
        super.viewDidLoad()
        self.navigationItem.hidesBackButton = true
        let newBackButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.plain, target: self, action: #selector(YourViewController.back(sender:)))
        self.navigationItem.leftBarButtonItem = newBackButton
    }

    func back(sender: UIBarButtonItem) {
        // Perform your custom actions
        // ...
        // Go back to the previous ViewController
        _ = navigationController?.popViewController(animated: true)
    }
fr33g
źródło
2
Nie wyskakuje to do poprzedniego kontrolera widoku; wyskakuje do kontrolera widoku głównego.
skalisty
91
Jak mogę uzyskać strzałkę jak zwykły przycisk Wstecz?
TomSawyer
@rocky Możesz wypróbować poniższą linię w funkcji powrotu: [self.navigationController dismissViewControllerAnimated: TAK ukończenie: zero];
malajisi
2
@TomSawyer W tym celu zapoznaj się z odpowiedzią poniżej
fr33g
7
Wykonanie zamiany przycisku systemowego w celu zastąpienia funkcji nie jest dobrym sposobem. Najlepszym sposobem jest odpowiedź poniżej! stackoverflow.com/a/27715660/2307276
dpizzuto
477

Zastąpienie przycisku niestandardowym, jak sugerowano w innej odpowiedzi, nie jest dobrym pomysłem, ponieważ stracisz domyślne zachowanie i styl.

Inną opcją jest zaimplementowanie metody viewWillDisappear w kontrolerze widoku i sprawdzenie właściwości o nazwie ismovingFromParentViewController . Jeśli ta właściwość jest prawdziwa, oznacza to, że kontroler widoku znika, ponieważ jest usuwany (otwierany).

Powinien wyglądać mniej więcej tak:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    if self.isMovingFromParentViewController {
        // Your code...
    }
}

W szybkim 4.2

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    if self.isMovingFromParent {
        // Your code...
    }
}
manecosta
źródło
5
@gmogames tak, nie możesz tego zrobić. Pytanie o to nie pytało. Aby móc zatrzymać akcję cofania się, myślę, że naprawdę musisz zastąpić przycisk.
manecosta
13
Dla Swift 3.1 :override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) if isMovingFromParentViewController { // Your code... } }
Doug Amos
23
viewWillDisappear(animated:)uruchomi się, jeśli otrzymasz połączenie telefoniczne. Prawdopodobnie nie tego chcesz. Prawdopodobnie lepszy w użyciuwillMove(toParentViewController:)
Joe Susnick
w Swift 4, brakuje : zastąp widok widoku FunDisappear ( animowane: Bool)
Javier Calatrava Llavería 24.04.2018
1
To powinna być zaakceptowana odpowiedź. Czysty i prosty.
temp.
60
override func willMove(toParent parent: UIViewController?)
{
    super.willMove(toParent: parent)
    if parent == nil
    {
        print("This VC is 'will' be popped. i.e. the back button was pressed.")
    }
}
Iya
źródło
2
Nie działa w Swift3 / iOS10, konsola drukuje „zagnieżdżona animacja pop może spowodować uszkodzenie paska nawigacji”.
itsji10dra
1
W ogóle nie
dzwoni
3
Jest to również wywoływane przy przeprowadzce do nowego VC, nie tylko podczas powrotu.
Jose Ramirez
Zgodnie z komentarzem @JozemiteApps, znajduje się w dokumentacji Called tuż przed dodaniem lub usunięciem kontrolera widoku z kontrolera widoku kontenera. .
nstein
2
To powinna być zaakceptowana odpowiedź. A kiedy parent == niljest kiedy ruszamy z powrotem na parentscenie
Sylvan D Ash
32

Udało mi się to osiągnąć w następujący sposób:

Szybki 3

override func didMoveToParentViewController(parent: UIViewController?) {
   super.didMoveToParentViewController(parent)

   if parent == nil {
      println("Back Button pressed.")
      delegate?.goingBack()
   }           
}

Szybki 4

override func didMove(toParent parent: UIViewController?) {
    super.didMove(toParent: parent)

    if parent == nil {
        debugPrint("Back Button pressed.")
    }
}

Nie ma potrzeby niestandardowego przycisku Wstecz.

Siddharth Bhatt
źródło
To fantastyka. Stara uwaga, ale nadal działa z najnowszym Swift.
user3204765,
Jest to wyzwalane (fałszywie dodatnie) również podczas odwijania z następnego kontrolera widoku (nad tym), więc tak naprawdę nie wykrywa naciśnięcia przycisku wstecz.
user2878850
Ta sama uwaga jak poprzednia osoba, ten kod nie wykrywa aktywacji przycisku Wstecz, ale wyskakuje bieżący widok.
Vilmir
31

Stworzyłem tę (szybką) klasę, aby utworzyć przycisk powrotu dokładnie tak jak zwykły, w tym strzałkę wstecz. Może utworzyć przycisk ze zwykłym tekstem lub obrazem.

Stosowanie

weak var weakSelf = self

// Assign back button with back arrow and text (exactly like default back button)
navigationItem.leftBarButtonItems = CustomBackButton.createWithText("YourBackButtonTitle", color: UIColor.yourColor(), target: weakSelf, action: #selector(YourViewController.tappedBackButton))

// Assign back button with back arrow and image
navigationItem.leftBarButtonItems = CustomBackButton.createWithImage(UIImage(named: "yourImageName")!, color: UIColor.yourColor(), target: weakSelf, action: #selector(YourViewController.tappedBackButton))

func tappedBackButton() {

    // Do your thing

    self.navigationController!.popViewControllerAnimated(true)
}

CustomBackButtonClass

(kod do rysowania strzałki wstecz utworzonej za pomocą wtyczki Sketch & Paintcode)

class CustomBackButton: NSObject {

    class func createWithText(text: String, color: UIColor, target: AnyObject?, action: Selector) -> [UIBarButtonItem] {
        let negativeSpacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FixedSpace, target: nil, action: nil)
        negativeSpacer.width = -8
        let backArrowImage = imageOfBackArrow(color: color)
        let backArrowButton = UIBarButtonItem(image: backArrowImage, style: UIBarButtonItemStyle.Plain, target: target, action: action)
        let backTextButton = UIBarButtonItem(title: text, style: UIBarButtonItemStyle.Plain , target: target, action: action)
        backTextButton.setTitlePositionAdjustment(UIOffset(horizontal: -12.0, vertical: 0.0), forBarMetrics: UIBarMetrics.Default)
        return [negativeSpacer, backArrowButton, backTextButton]
    }

    class func createWithImage(image: UIImage, color: UIColor, target: AnyObject?, action: Selector) -> [UIBarButtonItem] {
        // recommended maximum image height 22 points (i.e. 22 @1x, 44 @2x, 66 @3x)
        let negativeSpacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FixedSpace, target: nil, action: nil)
        negativeSpacer.width = -8
        let backArrowImageView = UIImageView(image: imageOfBackArrow(color: color))
        let backImageView = UIImageView(image: image)
        let customBarButton = UIButton(frame: CGRectMake(0,0,22 + backImageView.frame.width,22))
        backImageView.frame = CGRectMake(22, 0, backImageView.frame.width, backImageView.frame.height)
        customBarButton.addSubview(backArrowImageView)
        customBarButton.addSubview(backImageView)
        customBarButton.addTarget(target, action: action, forControlEvents: .TouchUpInside)
        return [negativeSpacer, UIBarButtonItem(customView: customBarButton)]
    }

    private class func drawBackArrow(frame frame: CGRect = CGRect(x: 0, y: 0, width: 14, height: 22), color: UIColor = UIColor(hue: 0.59, saturation: 0.674, brightness: 0.886, alpha: 1), resizing: ResizingBehavior = .AspectFit) {
        /// General Declarations
        let context = UIGraphicsGetCurrentContext()!

        /// Resize To Frame
        CGContextSaveGState(context)
        let resizedFrame = resizing.apply(rect: CGRect(x: 0, y: 0, width: 14, height: 22), target: frame)
        CGContextTranslateCTM(context, resizedFrame.minX, resizedFrame.minY)
        let resizedScale = CGSize(width: resizedFrame.width / 14, height: resizedFrame.height / 22)
        CGContextScaleCTM(context, resizedScale.width, resizedScale.height)

        /// Line
        let line = UIBezierPath()
        line.moveToPoint(CGPoint(x: 9, y: 9))
        line.addLineToPoint(CGPoint.zero)
        CGContextSaveGState(context)
        CGContextTranslateCTM(context, 3, 11)
        line.lineCapStyle = .Square
        line.lineWidth = 3
        color.setStroke()
        line.stroke()
        CGContextRestoreGState(context)

        /// Line Copy
        let lineCopy = UIBezierPath()
        lineCopy.moveToPoint(CGPoint(x: 9, y: 0))
        lineCopy.addLineToPoint(CGPoint(x: 0, y: 9))
        CGContextSaveGState(context)
        CGContextTranslateCTM(context, 3, 2)
        lineCopy.lineCapStyle = .Square
        lineCopy.lineWidth = 3
        color.setStroke()
        lineCopy.stroke()
        CGContextRestoreGState(context)

        CGContextRestoreGState(context)
    }

    private class func imageOfBackArrow(size size: CGSize = CGSize(width: 14, height: 22), color: UIColor = UIColor(hue: 0.59, saturation: 0.674, brightness: 0.886, alpha: 1), resizing: ResizingBehavior = .AspectFit) -> UIImage {
        var image: UIImage

        UIGraphicsBeginImageContextWithOptions(size, false, 0)
        drawBackArrow(frame: CGRect(origin: CGPoint.zero, size: size), color: color, resizing: resizing)
        image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return image
    }

    private enum ResizingBehavior {
        case AspectFit /// The content is proportionally resized to fit into the target rectangle.
        case AspectFill /// The content is proportionally resized to completely fill the target rectangle.
        case Stretch /// The content is stretched to match the entire target rectangle.
        case Center /// The content is centered in the target rectangle, but it is NOT resized.

        func apply(rect rect: CGRect, target: CGRect) -> CGRect {
            if rect == target || target == CGRect.zero {
                return rect
            }

            var scales = CGSize.zero
            scales.width = abs(target.width / rect.width)
            scales.height = abs(target.height / rect.height)

            switch self {
                case .AspectFit:
                    scales.width = min(scales.width, scales.height)
                    scales.height = scales.width
                case .AspectFill:
                    scales.width = max(scales.width, scales.height)
                    scales.height = scales.width
                case .Stretch:
                    break
                case .Center:
                    scales.width = 1
                    scales.height = 1
            }

            var result = rect.standardized
            result.size.width *= scales.width
            result.size.height *= scales.height
            result.origin.x = target.minX + (target.width - result.width) / 2
            result.origin.y = target.minY + (target.height - result.height) / 2
            return result
        }
    }
}

SWIFT 3.0

class CustomBackButton: NSObject {

    class func createWithText(text: String, color: UIColor, target: AnyObject?, action: Selector) -> [UIBarButtonItem] {
        let negativeSpacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.fixedSpace, target: nil, action: nil)
        negativeSpacer.width = -8
        let backArrowImage = imageOfBackArrow(color: color)
        let backArrowButton = UIBarButtonItem(image: backArrowImage, style: UIBarButtonItemStyle.plain, target: target, action: action)
        let backTextButton = UIBarButtonItem(title: text, style: UIBarButtonItemStyle.plain , target: target, action: action)
        backTextButton.setTitlePositionAdjustment(UIOffset(horizontal: -12.0, vertical: 0.0), for: UIBarMetrics.default)
        return [negativeSpacer, backArrowButton, backTextButton]
    }

    class func createWithImage(image: UIImage, color: UIColor, target: AnyObject?, action: Selector) -> [UIBarButtonItem] {
        // recommended maximum image height 22 points (i.e. 22 @1x, 44 @2x, 66 @3x)
        let negativeSpacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.fixedSpace, target: nil, action: nil)
        negativeSpacer.width = -8
        let backArrowImageView = UIImageView(image: imageOfBackArrow(color: color))
        let backImageView = UIImageView(image: image)
        let customBarButton = UIButton(frame: CGRect(x: 0, y: 0, width: 22 + backImageView.frame.width, height: 22))
        backImageView.frame = CGRect(x: 22, y: 0, width: backImageView.frame.width, height: backImageView.frame.height)
        customBarButton.addSubview(backArrowImageView)
        customBarButton.addSubview(backImageView)
        customBarButton.addTarget(target, action: action, for: .touchUpInside)
        return [negativeSpacer, UIBarButtonItem(customView: customBarButton)]
    }

    private class func drawBackArrow(_ frame: CGRect = CGRect(x: 0, y: 0, width: 14, height: 22), color: UIColor = UIColor(hue: 0.59, saturation: 0.674, brightness: 0.886, alpha: 1), resizing: ResizingBehavior = .AspectFit) {
        /// General Declarations
        let context = UIGraphicsGetCurrentContext()!

        /// Resize To Frame
        context.saveGState()
        let resizedFrame = resizing.apply(CGRect(x: 0, y: 0, width: 14, height: 22), target: frame)
        context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
        let resizedScale = CGSize(width: resizedFrame.width / 14, height: resizedFrame.height / 22)
        context.scaleBy(x: resizedScale.width, y: resizedScale.height)

        /// Line
        let line = UIBezierPath()
        line.move(to: CGPoint(x: 9, y: 9))
        line.addLine(to: CGPoint.zero)
        context.saveGState()
        context.translateBy(x: 3, y: 11)
        line.lineCapStyle = .square
        line.lineWidth = 3
        color.setStroke()
        line.stroke()
        context.restoreGState()

        /// Line Copy
        let lineCopy = UIBezierPath()
        lineCopy.move(to: CGPoint(x: 9, y: 0))
        lineCopy.addLine(to: CGPoint(x: 0, y: 9))
        context.saveGState()
        context.translateBy(x: 3, y: 2)
        lineCopy.lineCapStyle = .square
        lineCopy.lineWidth = 3
        color.setStroke()
        lineCopy.stroke()
        context.restoreGState()

        context.restoreGState()
    }

    private class func imageOfBackArrow(_ size: CGSize = CGSize(width: 14, height: 22), color: UIColor = UIColor(hue: 0.59, saturation: 0.674, brightness: 0.886, alpha: 1), resizing: ResizingBehavior = .AspectFit) -> UIImage {
        var image: UIImage

        UIGraphicsBeginImageContextWithOptions(size, false, 0)
        drawBackArrow(CGRect(origin: CGPoint.zero, size: size), color: color, resizing: resizing)
        image = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()

        return image
    }

    private enum ResizingBehavior {
        case AspectFit /// The content is proportionally resized to fit into the target rectangle.
        case AspectFill /// The content is proportionally resized to completely fill the target rectangle.
        case Stretch /// The content is stretched to match the entire target rectangle.
        case Center /// The content is centered in the target rectangle, but it is NOT resized.

        func apply(_ rect: CGRect, target: CGRect) -> CGRect {
            if rect == target || target == CGRect.zero {
                return rect
            }

            var scales = CGSize.zero
            scales.width = abs(target.width / rect.width)
            scales.height = abs(target.height / rect.height)

            switch self {
            case .AspectFit:
                scales.width = min(scales.width, scales.height)
                scales.height = scales.width
            case .AspectFill:
                scales.width = max(scales.width, scales.height)
                scales.height = scales.width
            case .Stretch:
                break
            case .Center:
                scales.width = 1
                scales.height = 1
            }

            var result = rect.standardized
            result.size.width *= scales.width
            result.size.height *= scales.height
            result.origin.x = target.minX + (target.width - result.width) / 2
            result.origin.y = target.minY + (target.height - result.height) / 2
            return result
        }
    }
}
guido
źródło
Czy byłbyś uprzejmy zaktualizować swoją odpowiedź na iOS 11?
BR41N-FCK
2
Cześć @guido, twoje rozwiązanie jest idealne, wypróbowałem Twój kod i zauważyłem, że przed przyciskiem Wstecz jest miejsce, mimo że dodałeś przycisk barbash z ujemną szerokością.
Pawriwes,
26

Jeśli chcesz mieć przycisk Wstecz ze strzałką Wstecz, możesz użyć obrazu i kodu poniżej

backArrow.png arrow1[email protected] arrow2[email protected]arrow3

override func viewDidLoad() {
    super.viewDidLoad()
    let customBackButton = UIBarButtonItem(image: UIImage(named: "backArrow") , style: .plain, target: self, action: #selector(backAction(sender:)))
    customBackButton.imageInsets = UIEdgeInsets(top: 2, left: -8, bottom: 0, right: 0)
    navigationItem.leftBarButtonItem = customBackButton
}

func backAction(sender: UIBarButtonItem) {
    // custom actions here
    navigationController?.popViewController(animated: true)
}
Leszek Szary
źródło
12

Jeśli używasz, navigationControllerdodaj UINavigationControllerDelegateprotokół do klasy i dodaj metodę delegowania w następujący sposób:

class ViewController:UINavigationControllerDelegate {

    func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController,
animated: Bool) {
        if viewController === self {
            // do here what you want
        }
    }
}

Ta metoda jest wywoływana za każdym razem, gdy kontroler nawigacyjny przejdzie do nowego ekranu. Jeśli przycisk Wstecz został naciśnięty, nowy kontroler widoku jest ViewControllersam.

Ajinkya Patil
źródło
Co staje się okropne, gdy używasz klasy innej niż NSObjectProtocol jako delegata.
Nick Weaver,
Nie zawsze jest wywoływane po naciśnięciu przycisku Wstecz.
Ted
9

W Swift 5 i Xcode 10.2

Nie dodawaj niestandardowego przycisku na pasku, użyj tego domyślnego zachowania.

Nie ma potrzeby widokuWillDisappear , nie ma potrzeby niestandardowego BarButtonItem itp.

Lepiej jest wykryć, kiedy VC jest usuwane z jego rodzica.

Użyj dowolnej z tych dwóch funkcji

override func willMove(toParent parent: UIViewController?) {
    super.willMove(toParent: parent)
    if parent == nil {
        callStatusDelegate?.backButtonClicked()//Here write your code
    }
}

override func didMove(toParent parent: UIViewController?) {
    super.didMove(toParent: parent)
    if parent == nil {
        callStatusDelegate?.backButtonClicked()//Here write your code
    }
}

Jeśli chcesz zatrzymać domyślne zachowanie przycisku Wstecz, dodaj niestandardowy BarButtonItem.

iOS
źródło
1
Pamiętaj, że jest to również wywoływane, gdy pop programowo, nie tylko naciśnij przycisk Wstecz.
Ixx
7

NIE

override func willMove(toParentViewController parent: UIViewController?) { }

Zostanie to wywołane, nawet jeśli przechodzisz do kontrolera widoku, w którym zastępujesz tę metodę. W którym sprawdzenie, czy „ parentnilnie jest, nie jest dokładnym sposobem na upewnienie się, że wrócisz do właściwego UIViewController. Aby dokładnie ustalić, czy UINavigationControllerprawidłowo nawiguje z powrotem do tego, UIViewControllerktóry prezentował ten bieżący, musisz dostosować się doUINavigationControllerDelegate protokołu.

TAK

Uwaga: MyViewControllerto tylko nazwa tego, co UIViewControllerchcesz wykryć, wracając.

1) Na górze pliku dodaj UINavigationControllerDelegate.

class MyViewController: UIViewController, UINavigationControllerDelegate {

2) Dodaj właściwość do swojej klasy, która będzie śledzić UIViewControllersegregację.

class MyViewController: UIViewController, UINavigationControllerDelegate {

var previousViewController:UIViewController

3) w MyViewController„s viewDidLoadmetody assign selfjako delegata Dla Państwa UINavigationController.

override func viewDidLoad() {
    super.viewDidLoad()
    self.navigationController?.delegate = self
}

3) Przed segregowaniem przypisz poprzednie UIViewControllerjako tę właściwość.

// In previous UIViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "YourSegueID" {
        if let nextViewController = segue.destination as? MyViewController {
            nextViewController.previousViewController = self
        }
    }
}

4) I zgodne z jedną metodą MyViewControllerzUINavigationControllerDelegate

func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
    if viewController == self.previousViewController {
        // You are going back
    }
}
Brandon A.
źródło
1
Dziękuję za pomocną odpowiedź! Czytelnicy wystrzegają się ustawiania delegata UINavigationController na określony kontroler widoku; jeśli kontroler nawigacyjny ma już delegata, istnieje ryzyko, że inny delegat wywołań zwrotnych będzie oczekiwany. W naszej aplikacji delegat UINavigationController jest współdzielonym obiektem (AppCoordinator), do którego wszystkie kontrolery widoku mają wskaźnik.
Bill Feth
7

W moim przypadku viewWillDisappeardziałało najlepiej. Ale w niektórych przypadkach trzeba zmodyfikować poprzedni kontroler widoku. Oto moje rozwiązanie z dostępem do poprzedniego kontrolera widoku i działa w Swift 4 :

override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        if isMovingFromParentViewController {
            if let viewControllers = self.navigationController?.viewControllers {
                if (viewControllers.count >= 1) {
                    let previousViewController = viewControllers[viewControllers.count-1] as! NameOfDestinationViewController
                    // whatever you want to do
                    previousViewController.callOrModifySomething()
                }
            }
        }
    }
Bernd
źródło
-viewDidDisappear (lub -viewWillDisappear) zostanie wywołany, nawet jeśli widok jest objęty widokiem innego kontrolera widoku (nie tylko po naciśnięciu przycisku <Wstecz), dlatego należy sprawdzić isMovingFromParentViewController.
Bill Feth,
5

Przed opuszczeniem obecnego kontrolera muszę pokazać alert. Zrobiłem to w ten sposób:

  1. Dodaj rozszerzenie do za UINavigationControllerpomocąUINavigationBarDelegate
  2. Dodaj selektor do nawigacji kontroleraShouldPopOnBack (zakończenie :)

Zadziałało)

extension UINavigationController: UINavigationBarDelegate {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        if let items = navigationBar.items, viewControllers.count < items.count {
            return true
        }

        let clientInfoVC = topViewController as? ClientInfoVC
        if clientInfoVC?.responds(to: #selector(clientInfoVC?.navigationShouldPopOnBack)) ?? false {
            clientInfoVC?.navigationShouldPopOnBack(completion: { isAllowPop in
                if isAllowPop {
                    DispatchQueue.main.async {
                        self.popViewController(animated: true)
                    }
                }
            })
        }

        DispatchQueue.main.async {
            self.popViewController(animated: true)
        }

        return false
    }
}

@objc func navigationShouldPopOnBack(completion: @escaping (Bool) -> ()) {
        let ok = UIAlertAction(title: R.string.alert.actionOk(), style: .default) { _ in
            completion(true)
        }
        let cancel = UIAlertAction(title: R.string.alert.actionCancel(), style: .cancel) { _ in
            completion(false)
        }
        let alertController = UIAlertController(title: "", message: R.string.alert.contractMessage(), preferredStyle: .alert)
        alertController.addAction(ok)
        alertController.addAction(cancel)
        present(alertController, animated: true, completion: nil)
    }
Taras
źródło
4

To nie jest trudne, jak my. Po prostu utwórz ramkę dla UIButton z wyraźnym kolorem tła, przypisz akcję do przycisku i umieść nad przyciskiem wstecz paska nawigacji. I na koniec usuń przycisk po użyciu.

Oto przykładowy kod Swift 3 wykonany za pomocą UIImage zamiast UIButton

override func viewDidLoad() {
    super.viewDidLoad()
    let imageView = UIImageView()
    imageView.backgroundColor = UIColor.clear
    imageView.frame = CGRect(x:0,y:0,width:2*(self.navigationController?.navigationBar.bounds.height)!,height:(self.navigationController?.navigationBar.bounds.height)!)
    let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(back(sender:)))
    imageView.isUserInteractionEnabled = true
    imageView.addGestureRecognizer(tapGestureRecognizer)
    imageView.tag = 1
    self.navigationController?.navigationBar.addSubview(imageView)
    }

napisz kod należy wykonać

func back(sender: UIBarButtonItem) {

    // Perform your custom actions}
    _ = self.navigationController?.popViewController(animated: true)

    }

Usuń widok podrzędny po wykonaniu akcji

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    for view in (self.navigationController?.navigationBar.subviews)!{
        if view.tag == 1 {
            view.removeFromSuperview()
        }
    }
ARSHWIN DENUEV LAL
źródło
Dzięki stary . :-)
ARSHWIN DENUEV LAL
Jak stworzyć stan po przyziemieniu?
quang thang
Nie działa to w iOS 11. Nie, gdy kolor tła UIImageView jest wyraźny. Ustaw inny kolor i działa.
Dotknij Forms
Możemy zdefiniować UIImageView wyraźnym kolorem, ustawić jego ramkę, przypisać tapesture i umieścić w dowolnym miejscu na ekranie. Dlaczego więc nie możemy umieścić go nad paskiem nawigacji. Szczerze mówiąc nie polecę tego, co napisałem. Jeśli jest jakiś problem, to z pewnością jest powód, ale nie ma znaczenia kolor. Zapomnij o kodzie zgodnie z logiką, że się powiedzie. :)
ARSHWIN DENUEV LAL
4

Swift 4.2:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    if self.isMovingFromParent {
        // Your code...

    }
}
Md. Najmul Hasan
źródło
3

Swift 3:

override func didMove(toParentViewController parent: UIViewController?) {
    super.didMove(toParentViewController: parent)

    if parent == nil{
        print("Back button was clicked")
    }
}
Gal
źródło
-did / willMove (toParentViewController :) jest prawdopodobnie lepsze niż sprawdzanie isMovingTfromParentViewController w opcji -viewWillDisappear, ponieważ jest wywoływane tylko wtedy, gdy kontroler widoku faktycznie zmienia rodziców (nie kiedy widok jest objęty widokiem innego VC) Ale bardziej „poprawnym” rozwiązaniem jest zaimplementować metodę delegowania UINavigationController. Bądź jednak ostrożny; jeśli NavigationController ma już delegata, istnieje ryzyko, że inny delegat wywołań zwrotnych będzie oczekiwany.
Bill Feth
Testowałem z splitViewController. Tam nie można odróżnić dodanego lub usuniętego.
claude31
2

Spróbuj tego .

self.navigationItem.leftBarButtonItem?.target = "methodname"
func methodname ( ) {            
  //    enter code here
}

Spróbuj tego też.

override func viewWillAppear(animated: Bool) {
  //empty your array
}
AG
źródło
2

po prostu kontroluj + przeciągnij element paska poniżej func. działa jak urok

@IBAction func done(sender: AnyObject) {
    if((self.presentingViewController) != nil){
        self.dismiss(animated: false, completion: nil)
        print("done")
    }
}

wprowadź opis zdjęcia tutaj

kodery
źródło
2

Możesz podklasować UINavigationControlleri zastępować popViewController(animated: Bool). Oprócz możliwości wykonania jakiegoś kodu, możesz również uniemożliwić użytkownikowi całkowite cofnięcie się, na przykład, aby poprosić o zapisanie lub odrzucenie jego bieżącej pracy.

Przykładowa implementacja, w której można ustawić ustawienie, popHandlerktóre zostanie ustawione / wyczyszczone przez kontrolery wypychane.

class NavigationController: UINavigationController
{
    var popHandler: (() -> Bool)?

    override func popViewController(animated: Bool) -> UIViewController?
    {
        guard self.popHandler?() != false else
        {
            return nil
        }
        self.popHandler = nil
        return super.popViewController(animated: animated)
    }
}

I przykładowe użycie z kontrolera wypychanego, który śledzi niezapisaną pracę.

let hasUnsavedWork: Bool = // ...
(self.navigationController as! NavigationController).popHandler = hasUnsavedWork ?
    {
        // Prompt saving work here with an alert

        return false // Prevent pop until as user choses to save or discard

    } : nil // No unsaved work, we clear popHandler to let it pop normally

Przyjemnym akcentem będzie również wywołanie, interactivePopGestureRecognizergdy użytkownik spróbuje wrócić gestem machnięcia.

Rivera
źródło
najlepsza odpowiedź, Dzięki Rivera
DvixExtract
2

To jest moje rozwiązanie

extension UINavigationController: UINavigationBarDelegate {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        if let shouldBlock = self.topViewController?.shouldPopFromNavigation() {
            return shouldBlock
        }
        return true
    }
}

extension UIViewController {
    @objc func shouldPopFromNavigation() -> Bool {
        return true
    }
}

W kontrolerze widoku możesz obsługiwać w następujący sposób:

@objc override func shouldPopFromNavigation() -> Bool {
        // Your dialog, example UIAlertViewController or whatever you want
        return false
    }
Hiro
źródło
1

Jak rozumiem chcesz opróżnić arrayjak naciśniesz przycisk Wstecz i pop do poprzedniej ViewController letlisty Array, który został załadowany na tym ekranie jest

let settingArray  = NSMutableArray()
@IBAction func Back(sender: AnyObject) {
    self. settingArray.removeAllObjects()
    self.dismissViewControllerAnimated(true, completion: nil)
} 
Avinash Mishra
źródło
1
    override public func viewDidLoad() {
         super.viewDidLoad()
         self.navigationController?.navigationBar.topItem?.title = GlobalVariables.selectedMainIconName
         let image = UIImage(named: "back-btn")

         image = image?.imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal)

        self.navigationItem.leftBarButtonItem = UIBarButtonItem(image: image, style: UIBarButtonItemStyle.Plain, target: self, action: #selector(Current[enter image description here][1]ViewController.back) )
    }

    func back() {
      self.navigationController?.popToViewController( self.navigationController!.viewControllers[ self.navigationController!.viewControllers.count - 2 ], animated: true)
    }
Shahkar
źródło
1
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    if self.isMovingToParent {

        //your code backView
    }
}
Papon Smc
źródło
1

W przypadku Swift 5 możemy sprawdzić, czy widok zniknie

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    if self.isMovingFromParent {
        delegate?.passValue(clickedImage: selectedImage)
    }
}
Sandu
źródło
1

Swift 5 __ Xcode 11.5

W moim przypadku chciałem zrobić animację, a kiedy się skończy, wróć. Sposób zastąpienia domyślnej akcji przycisku Wstecz i wywołania akcji niestandardowej jest następujący:

     override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        setBtnBack()
    }

    private func setBtnBack() {
        for vw in navigationController?.navigationBar.subviews ?? [] where "\(vw.classForCoder)" == "_UINavigationBarContentView" {
            print("\(vw.classForCoder)")
            for subVw in vw.subviews where "\(subVw.classForCoder)" == "_UIButtonBarButton" {
                let ctrl = subVw as! UIControl
                ctrl.removeTarget(ctrl.allTargets.first, action: nil, for: .allEvents)
                ctrl.addTarget(self, action: #selector(backBarBtnAction), for: .touchUpInside)
            }
        }
    }


    @objc func backBarBtnAction() {
        doSomethingBeforeBack { [weak self](isEndedOk) in
            if isEndedOk {
                self?.navigationController?.popViewController(animated: true)
            }
        }
    }


    private func doSomethingBeforeBack(completion: @escaping (_ isEndedOk:Bool)->Void ) {
        UIView.animate(withDuration: 0.25, animations: { [weak self] in
            self?.vwTxt.alpha = 0
        }) { (isEnded) in
            completion(isEnded)
        }
    }

Hierarchia widoku paska nawigacji

Możesz też użyć tej metody jeden raz, aby zbadać hierarchię widoku paska nawigacji i uzyskać indeksy, aby uzyskać dostęp do widoku _UIButtonBarButton, rzutować na UIControl, usunąć akcję docelową i dodać własne akcje docelowe:

    private func debug_printSubviews(arrSubviews:[UIView]?, level:Int) {
        for (i,subVw) in (arrSubviews ?? []).enumerated() {
            var str = ""
            for _ in 0...level {
                str += "\t"
            }
            str += String(format: "%2d %@",i, "\(subVw.classForCoder)")
            print(str)
            debug_printSubviews(arrSubviews: subVw.subviews, level: level + 1)
        }
    }

    // Set directly the indexs
    private func setBtnBack_method2() {
        // Remove or comment the print lines
        debug_printSubviews(arrSubviews: navigationController?.navigationBar.subviews, level: 0)   
        let ctrl = navigationController?.navigationBar.subviews[1].subviews[0] as! UIControl
        print("ctrl.allTargets: \(ctrl.allTargets)")
        ctrl.removeTarget(ctrl.allTargets.first, action: nil, for: .allEvents)
        print("ctrl.allTargets: \(ctrl.allTargets)")
        ctrl.addTarget(self, action: #selector(backBarBtnAction), for: .touchUpInside)
        print("ctrl.allTargets: \(ctrl.allTargets)")
    }
Miguel Gallego
źródło
0

Osiągnąłem to, wywołując / zastępując, viewWillDisappeara następnie uzyskując dostęp do stosu navigationController:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)

    let stack = self.navigationController?.viewControllers.count

    if stack >= 2 {
        // for whatever reason, the last item on the stack is the TaskBuilderViewController (not self), so we only use -1 to access it
        if let lastitem = self.navigationController?.viewControllers[stack! - 1] as? theViewControllerYoureTryingToAccess {
            // hand over the data via public property or call a public method of theViewControllerYoureTryingToAccess, like
            lastitem.emptyArray()
            lastitem.value = 5
        }
    }
}
NerdyTherapist
źródło
0

W ten sposób rozwiązałem go dla własnego problemu

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    self.navigationItem.leftBarButtonItem?.action = #selector(self.back(sender:))
    self.navigationItem.leftBarButtonItem?.target = self
}

@objc func back(sender: UIBarButtonItem) {

}
amorenew
źródło
0

Oto najprostsze możliwe rozwiązanie Swift 5, które nie wymaga tworzenia niestandardowego przycisku Wstecz i rezygnacji z wszystkich funkcji lewego przycisku UINavigationController, które otrzymujesz za darmo.

Jak zaleca Brandon A powyżej, musisz UINavigationControllerDelegatewrócić do kontrolera widoku, z którym chcesz wchodzić w interakcję, zanim wrócisz do niego. Dobrym sposobem jest utworzenie segmentu odwijania, który można wykonać ręcznie lub automatycznie i ponownie użyć tego samego kodu z niestandardowego przycisku lub przycisku Wstecz.

Po pierwsze, ustaw kontroler widoku będący przedmiotem zainteresowania (ten, do którego chcesz wykryć powrót) na delegata kontrolera nawigacyjnego w jego viewDidLoad:

override func viewDidLoad() {
    super.viewDidLoad()
    navigationController?.delegate = self
}

Po drugie, dodaj rozszerzenie na dole pliku, które zastępuje navigationController(willShow:animated:)

extension PickerTableViewController: UINavigationControllerDelegate {

    func navigationController(_ navigationController: UINavigationController,
                              willShow viewController: UIViewController,
                              animated: Bool) {

        if let _ = viewController as? EditComicBookViewController {

            let selectedItemRow = itemList.firstIndex(of: selectedItemName)
            selectedItemIndex = IndexPath(row: selectedItemRow!, section: 0)

            if let selectedCell = tableView.cellForRow(at: selectedItemIndex) {
                performSegue(withIdentifier: "PickedItem", sender: selectedCell)
            }
        }
    }
}

Ponieważ twoje pytanie zawierało a UITableViewController, podałem sposób uzyskania ścieżki indeksu wiersza, który użytkownik dotknął.

John Pavley
źródło