Führen Sie die Aktion aus, wenn die Schaltfläche für die hintere Leiste von UINavigationController gedrückt wird


204

Ich muss eine Aktion ausführen (ein Array leeren), wenn die Zurück-Taste von a UINavigationControllergedrückt wird, während die Taste weiterhin die vorherige ViewControllerauf dem Stapel anzeigt. Wie könnte ich dies mit Swift erreichen? Geben Sie hier die Bildbeschreibung ein

Antworten:


152

Eine Möglichkeit wäre die Implementierung einer eigenen benutzerdefinierten Zurück-Schaltfläche. Sie müssten Ihrer viewDidLoad-Methode den folgenden Code hinzufügen:

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

AKTUALISIEREN:

Hier ist die Version für 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)
    }

UPDATE 2:

Hier ist die Version für 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)
    }

2
Dies wird nicht auf den vorherigen Ansichts-Controller übertragen. Es wird auf dem Root-View-Controller angezeigt.
Rocky

91
Wie kann ich einen Pfeil wie einen normalen Zurück-Button haben?
TomSawyer

@rocky Sie können die folgende Zeile in der Back-Funktion ausprobieren: [self.navigationController EntlassungViewControllerAnimated: YES Vervollständigung: Null];
Malajisi

2
@ TomSawyer Dafür schauen Sie sich bitte die Antwort unten an
fr33g

7
Das Ersetzen einer Systemschaltfläche zum Überschreiben einer Funktion ist kein guter Weg. Der beste Weg ist die Antwort unten! stackoverflow.com/a/27715660/2307276
dpizzuto

477

Das Ersetzen der Schaltfläche durch eine benutzerdefinierte Schaltfläche, wie in einer anderen Antwort vorgeschlagen, ist möglicherweise keine gute Idee, da Sie das Standardverhalten und den Standardstil verlieren.

Eine weitere Option besteht darin, die viewWillDisappear- Methode auf dem View Controller zu implementieren und nach einer Eigenschaft mit dem Namen isMovingFromParentViewController zu suchen . Wenn diese Eigenschaft wahr ist, bedeutet dies, dass der View Controller verschwindet, weil er entfernt (gepoppt) wird.

Sollte ungefähr so ​​aussehen:

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

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

In Kürze 4.2

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

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

5
@gmogames ja, das kannst du nicht. Die Frage hat das allerdings nicht gestellt. Um die Aktion des Zurückgehens stoppen zu können, müssen Sie den Button wirklich überschreiben.
Manecosta

13
Für Swift 3.1 :override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) if isMovingFromParentViewController { // Your code... } }
Doug Amos

23
viewWillDisappear(animated:)wird ausgelöst, wenn Sie einen Anruf erhalten. Dies ist wahrscheinlich nicht das, was Sie wollen. Wahrscheinlich besser zu bedienenwillMove(toParentViewController:)
Joe Susnick

in Swift 4 fehlt : überschreiben func viewWillDisappear ( animiert: Bool)
Javier Calatrava Llavería

1
Dies sollte die akzeptierte Antwort sein. Sauber und einfach.
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.")
    }
}

2
Wenn die Konsole in Swift3 / iOS10 nicht funktioniert, werden verschachtelte Pop-Animationen gedruckt, die zu einer beschädigten Navigationsleiste führen können.
itsji10dra


3
Dies wird auch beim Umzug in eine neue VC aufgerufen, nicht nur beim Zurückgehen.
Jose Ramirez

Gemäß dem Kommentar von @JozemiteApps befindet es sich in den Dokumenten , die unmittelbar vor dem Hinzufügen oder Entfernen des Ansichts-Controllers zu einem Container-Ansichts-Controller aufgerufen werden. .
nstein

2
Dies sollte die akzeptierte Antwort sein. Und wenn parent == nil, wenn wir ziehen zurück in die parentSzene
Sylvan D Ash

32

Dies konnte ich folgendermaßen erreichen:

Swift 3

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

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

Swift 4

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

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

Keine benutzerdefinierte Zurück-Taste erforderlich.


Das ist fantastisch. Alte Bemerkung funktioniert aber immer noch mit dem neuesten Swift.
user3204765

Dies wird auch beim Abwickeln vom nächsten Ansichts-Controller (über diesem) ausgelöst (falsch positiv), sodass die Erkennung des Zurück-Tastendrucks nicht wirklich erfolgt.
user2878850

Dieselbe Bemerkung wie bei der vorherigen Person. Dieser Code erkennt nicht die Aktivierung der Zurück-Schaltfläche, sondern das Popup der aktuellen Ansicht.
Vilmir

31

Ich habe diese (schnelle) Klasse erstellt, um eine Zurück-Schaltfläche genau wie die reguläre zu erstellen, einschließlich des Zurück-Pfeils. Es kann eine Schaltfläche mit normalem Text oder mit einem Bild erstellen.

Verwendung

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

(Code zum Zeichnen des mit dem Sketch & Paintcode-Plugin erstellten Zurückpfeils)

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

Würden Sie so freundlich sein, Ihre Antwort für iOS 11 zu aktualisieren?
BR41N-FCK

2
Hallo @guido, deine Lösung ist perfekt. Ich habe deinen Code ausprobiert und festgestellt, dass vor dem Zurück-Button Platz ist, obwohl du einen Barbutton mit negativer Breite hinzugefügt hast.
Pawriwes

26

Wenn Sie eine Zurück-Schaltfläche mit einem Zurück-Pfeil haben möchten, können Sie unten ein Bild und einen Code verwenden

backArrow.png Pfeil1backArrow@2x.png Pfeil2backArrow@3x.pngPfeil3

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

12

Wenn Sie verwenden, navigationControllerfügen Sie das UINavigationControllerDelegateProtokoll zur Klasse hinzu und fügen Sie die Delegatmethode wie folgt hinzu:

class ViewController:UINavigationControllerDelegate {

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

Diese Methode wird immer dann aufgerufen, wenn der Navigationscontroller auf einen neuen Bildschirm wechselt. Wenn die Zurück-Taste gedrückt wurde, ist der neue Ansichts-Controller ViewControllerselbst.


Was schrecklich wird, wenn eine Nicht-NSObjectProtocol-Klasse als Delegat verwendet wird.
Nick Weaver

Es wird nicht immer aufgerufen, wenn die Zurück-Taste gedrückt wird.
Ted

9

In Swift 5 und Xcode 10.2

Bitte fügen Sie kein benutzerdefiniertes Balkenschaltflächenelement hinzu. Verwenden Sie dieses Standardverhalten.

Keine Notwendigkeit für viewWillDisappear , keine Notwendigkeit für benutzerdefiniertes BarButtonItem etc ...

Es ist besser zu erkennen, wann der VC von seinem übergeordneten Element entfernt wird.

Verwenden Sie eine dieser beiden Funktionen

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

Wenn Sie das Standardverhalten der Zurück-Schaltfläche beenden möchten, fügen Sie ein benutzerdefiniertes BarButtonItem hinzu.


1
Beachten Sie, dass dies auch aufgerufen wird, wenn Sie programmgesteuert einblenden und nicht nur die Zurück-Taste drücken.
Ixx

7

NEIN

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

Dies wird auch dann aufgerufen, wenn Sie zu dem Ansichts-Controller wechseln , in dem Sie diese Methode überschreiben. In welcher überprüfen , ob die „ parent“ ist nilnicht die keine genaue Art und Weise ist bewegter sicher sein wieder auf den richtigen UIViewController. Um genau festzustellen, ob der UINavigationControllerrichtig zurück zum navigiertUIViewController demjenigen , das dieses aktuelle Modell präsentiert, müssen Sie sich an das anpassenUINavigationControllerDelegate Protokoll einhalten.

JA

Hinweis: MyViewControllerist nur der Name von was auch immerUIViewController Sie zurückgehen möchten.

1) Fügen Sie oben in Ihrer Datei hinzu UINavigationControllerDelegate.

class MyViewController: UIViewController, UINavigationControllerDelegate {

2) Fügen Sie Ihrer Klasse eine Eigenschaft hinzu, die den Überblick über die Eigenschaft behält, von der UIViewControllerSie abweichen.

class MyViewController: UIViewController, UINavigationControllerDelegate {

var previousViewController:UIViewController

3) in MyViewController's viewDidLoadMethode selfals Delegat für Ihre zuweisen UINavigationController.

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

3) Weisen Sie vor dem Übergang die vorherige UIViewControllerals diese Eigenschaft zu.

// 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) Und entsprechen einer Methode in MyViewControllerderUINavigationControllerDelegate

func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
    if viewController == self.previousViewController {
        // You are going back
    }
}

1
Danke für die hilfreiche Antwort! Leser achten darauf, den Delegaten des UINavigationControllers nicht auf einen bestimmten View Controller festzulegen. Wenn der Navigationscontroller bereits einen Delegaten hat, besteht die Gefahr, dass Sie diesem anderen Delegierten die erwarteten Rückrufe entziehen. In unserer App ist der Delegat des UINavigationControllers ein freigegebenes Objekt (ein AppCoordinator), auf das alle View Controller einen Zeiger haben.
Bill Feth

7

In meinem Fall hat das am viewWillDisappearbesten funktioniert. In einigen Fällen muss jedoch der vorherige Ansichts-Controller geändert werden. Hier ist meine Lösung mit Zugriff auf den vorherigen Ansichts-Controller , die in Swift 4 funktioniert :

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

-viewDidDisappear (oder -viewWillDisappear) wird auch dann aufgerufen, wenn die Ansicht von der Ansicht eines anderen Ansichtscontrollers abgedeckt wird (nicht nur, wenn die Schaltfläche <Zurück gedrückt wird). Daher muss isMovingFromParentViewController überprüft werden.
Bill Feth

5

Bevor ich den Stromregler verlasse, muss ich einen Alarm anzeigen. Also habe ich es so gemacht:

  1. Erweiterung UINavigationControllermit hinzufügenUINavigationBarDelegate
  2. Fügen Sie Ihrer Controller- Navigation einen Selektor hinzuShouldPopOnBack (Abschluss :)

Es hat funktioniert)

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

4

Es ist nicht schwer wie wir. Erstellen Sie einfach einen Rahmen für UIButton mit klarer Hintergrundfarbe, weisen Sie der Schaltfläche eine Aktion zu und platzieren Sie sie über der Zurück-Schaltfläche der Navigationsleiste. Und schließlich entfernen Sie den Knopf nach dem Gebrauch.

Hier ist der Swift 3-Beispielcode, der mit UIImage anstelle von UIButton erstellt wurde

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

Schreiben Sie den Code müssen ausgeführt werden

func back(sender: UIBarButtonItem) {

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

    }

Entfernen Sie die Unteransicht, nachdem die Aktion ausgeführt wurde

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

    for view in (self.navigationController?.navigationBar.subviews)!{
        if view.tag == 1 {
            view.removeFromSuperview()
        }
    }

Danke Alter. :-)
ARSHWIN DENUEV LAL

Wie erstellen Sie einen Status beim Aufsetzen?
Quang Thang

Dies scheint in iOS 11 nicht zu funktionieren. Nicht, wenn die Hintergrundfarbe von UIImageView klar ist. Stellen Sie eine andere Farbe ein und es funktioniert.
Tippen Sie auf Forms

Wir können eine UIImageView mit klarer Farbe definieren, ihren Rahmen festlegen, Tapgesture zuweisen und irgendwo auf dem Bildschirm platzieren. Warum können wir es dann nicht über einer Navigationsleiste platzieren? Um ehrlich zu sein, werde ich nicht empfehlen, was ich geschrieben habe. Wenn es definitiv ein Problem gibt, gibt es einen Grund, aber es kommt nicht auf die Farbe an. Vergessen Sie den Code, folgen Sie der Logik, die u erfolgreich sein wird. :)
ARSHWIN DENUEV LAL

4

Swift 4.2:

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

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

    }
}

3

Swift 3:

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

    if parent == nil{
        print("Back button was clicked")
    }
}

-did / willMove (toParentViewController :) ist möglicherweise besser als das Überprüfen von isMovingTfromParentViewController in -viewWillDisappear, da es nur aufgerufen wird, wenn der View-Controller tatsächlich die Eltern wechselt (nicht, wenn die Ansicht von der Ansicht eines anderen VC abgedeckt wird). Die "korrektere" Lösung ist jedoch um die UINavigationController-Delegatmethode zu implementieren. Sei jedoch vorsichtig; Wenn der NavigationController bereits einen Delegaten hat, besteht die Gefahr, dass Sie diesem anderen Delegierten die erwarteten Rückrufe entziehen.
Bill Feth

Ich habe mit einem splitViewController getestet. Dort konnte nicht der Unterschied zwischen hinzugefügt oder entfernt werden.
claude31

2

Versuche dies .

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

Probieren Sie das auch an.

override func viewWillAppear(animated: Bool) {
  //empty your array
}

2

Steuerung + Ziehen Sie das Balkenelement einfach unter die Funktion. arbeite wie Charme

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

Geben Sie hier die Bildbeschreibung ein


2

Sie können Unterklassen erstellen UINavigationControllerund überschreiben popViewController(animated: Bool). Sie können dort nicht nur Code ausführen, sondern auch verhindern, dass der Benutzer vollständig zurückkehrt, z. B. um zum Speichern oder Verwerfen seiner aktuellen Arbeit aufzufordern.

Beispielimplementierung, in der Sie eine popHandlerfestlegen können, die von Push-Controllern festgelegt / gelöscht wird.

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

Und Beispielnutzung von einem Push-Controller, der nicht gespeicherte Arbeit verfolgt.

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

Als nette interactivePopGestureRecognizerGeste wird dies auch aufgerufen, wenn der Benutzer versucht, mit einer Wischgeste zurückzukehren.


Überlegene Antwort, Danke Rivera
DvixExtract

2

Das ist meine Lösung

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

In Ihrem View Controller können Sie wie folgt vorgehen:

@objc override func shouldPopFromNavigation() -> Bool {
        // Your dialog, example UIAlertViewController or whatever you want
        return false
    }

1

Soweit ich weiß, möchten arraySie Ihre leeren, während Sie die Zurück-Taste drücken und zu Ihrer vorherigen wechselnViewController let Ihre , Arraydie Sie geladen auf diesem Bildschirm ist

let settingArray  = NSMutableArray()
@IBAction func Back(sender: AnyObject) {
    self. settingArray.removeAllObjects()
    self.dismissViewControllerAnimated(true, completion: nil)
} 

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

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

    if self.isMovingToParent {

        //your code backView
    }
}

1

Für Swift 5 können wir überprüfen, ob die Ansicht verschwindet

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

    if self.isMovingFromParent {
        delegate?.passValue(clickedImage: selectedImage)
    }
}

1

Swift 5 __ Xcode 11.5

In meinem Fall wollte ich eine Animation erstellen und wenn sie fertig ist, gehe zurück. Eine Möglichkeit, die Standardaktion der Zurück-Schaltfläche zu überschreiben und Ihre benutzerdefinierte Aktion aufzurufen, ist folgende:

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

Hierarchie der Navigationsleistenansicht

Sie können diese Methode auch einmal verwenden, um die Hierarchie der Navigationsleistenansicht zu untersuchen und die Indizes für den Zugriff auf die Ansicht _UIButtonBarButton abzurufen, in UIControl umzuwandeln, die Zielaktion zu entfernen und Ihre benutzerdefinierten Zielaktionen hinzuzufügen:

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

0

Ich habe dies erreicht, indem ich aufgerufen / überschrieben viewWillDisappearund dann auf den Stapel der folgenden navigationControllerArt zugegriffen habe:

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

0

So habe ich es für mein eigenes Problem gelöst

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

}

0

Hier ist die einfachste Swift 5-Lösung, bei der Sie keine benutzerdefinierte Zurück-Schaltfläche erstellen und auf die kostenlose Funktionalität der linken Taste von UINavigationController verzichten müssen.

Wie von Brandon A oben empfohlen, müssen Sie UINavigationControllerDelegateden View Controller, mit dem Sie interagieren möchten, implementieren, bevor Sie zu ihm zurückkehren. Eine gute Möglichkeit besteht darin, einen Abwicklungsbereich zu erstellen, den Sie manuell oder automatisch ausführen können, und denselben Code über eine benutzerdefinierte Schaltfläche oder die Schaltfläche "Zurück" wiederzuverwenden.

Machen Sie Ihren View Controller von Interesse (denjenigen, zu dem Sie zurückkehren möchten) zu einem Delegaten des Navigationscontrollers in seiner viewDidLoad:

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

Fügen Sie zweitens am Ende der Datei eine Erweiterung hinzu, die überschrieben wird 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)
            }
        }
    }
}

Da Ihre Frage a enthielt UITableViewController, habe ich eine Möglichkeit eingefügt, den Indexpfad der Zeile abzurufen, auf die der Benutzer getippt hat.


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.