Exécuter une action lorsque le bouton de la barre arrière de UINavigationController est enfoncé

204

J'ai besoin d'exécuter une action (vider un tableau), lorsque le bouton de retour de a UINavigationControllerest enfoncé, tandis que le bouton fait toujours ViewControllerapparaître le précédent sur la pile. Comment pourrais-je accomplir cela en utilisant Swift? entrez la description de l'image ici

StevenR
la source

Réponses:

152

Une option consisterait à implémenter votre propre bouton de retour personnalisé. Vous devez ajouter le code suivant à votre méthode 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];
}

METTRE À JOUR:

Voici la version pour 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)
    }

MISE À JOUR 2:

Voici la version pour 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
la source
2
Cela ne revient pas au contrôleur de vue précédent; il apparaît sur le contrôleur de vue racine.
rocky
91
Comment puis-je avoir une flèche comme un bouton de retour ordinaire?
TomSawyer
@rocky Vous pouvez essayer la ligne ci-dessous dans la fonction arrière: [self.navigationController rejetViewControllerAnimated: OUI achèvement: néant];
malajisi
2
@TomSawyer Pour cela, veuillez consulter la réponse ci
fr33g
7
Faire une substitution d'un bouton système pour remplacer une fonctionnalité n'est pas un bon moyen. La meilleure façon est la réponse ci-dessous! stackoverflow.com/a/27715660/2307276
dpizzuto
478

Remplacer le bouton par un bouton personnalisé comme suggéré dans une autre réponse n'est peut-être pas une bonne idée car vous perdrez le comportement et le style par défaut.

Vous pouvez également implémenter la méthode viewWillDisappear sur le contrôleur de vue et rechercher une propriété nommée isMovingFromParentViewController. . Si cette propriété est vraie, cela signifie que le contrôleur de vue disparaît car il est supprimé (sauté).

Devrait ressembler à quelque chose comme:

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

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

Dans swift 4.2

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

    if self.isMovingFromParent {
        // Your code...
    }
}
manecosta
la source
5
@gmogames oui, vous ne pouvez pas faire ça. Mais la question ne le demandait pas. Pour pouvoir arrêter l'action de revenir en arrière, je suppose que vous devez vraiment remplacer le bouton.
manecosta
13
Pour Swift 3.1 :override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) if isMovingFromParentViewController { // Your code... } }
Doug Amos
23
viewWillDisappear(animated:)sera déclenché si vous recevez un appel téléphonique. Ce n'est probablement pas ce que vous voulez. Probablement mieux à utiliserwillMove(toParentViewController:)
Joe Susnick
dans Swift 4, manquant : remplacer la vue funcWillDisappear ( animé: Bool)
Javier Calatrava Llavería
1
Cela devrait être la réponse acceptée. Propre et simple.
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
la source
2
Ne fonctionnant pas dans Swift3 / iOS10, la console imprime «l'animation pop imbriquée peut entraîner une barre de navigation corrompue».
itsji10dra
1
Ne pas être appelé du tout
zulkarnain shah
3
Cela est également appelé lors du passage dans un nouveau VC, pas seulement lors du retour.
Jose Ramirez du
Selon le commentaire de @JozemiteApps, il se trouve dans la documentation appelée juste avant l'ajout ou la suppression du contrôleur de vue d'un contrôleur de vue de conteneur. .
nstein
2
Cela devrait être la réponse acceptée. Et quand parent == nilest quand nous nous déplaçons de retour à la parentscène
Sylvan D Ash
32

J'ai pu y parvenir avec les éléments suivants:

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

Pas besoin de bouton de retour personnalisé.

Siddharth Bhatt
la source
Ce sont des fantastiques. Vieille remarque mais fonctionne toujours avec le dernier Swift.
user3204765
Ceci est déclenché (faux positif) également lors du déroulement du contrôleur de vue suivant (sur celui-ci), donc pas vraiment de détection de pression sur le bouton de retour.
user2878850
Même remarque que la personne précédente, ce code ne détecte pas l'activation du bouton retour, mais le pop de la vue courante.
Vilmir
31

J'ai créé cette classe (rapide) pour créer un bouton de retour exactement comme celui normal, y compris la flèche de retour. Il peut créer un bouton avec du texte normal ou avec une image.

Usage

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 pour dessiner la flèche arrière créé avec le plugin 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
la source
Seriez-vous si gentil de mettre à jour votre réponse pour iOS 11?
BR41N-FCK
2
Salut @guido, votre solution est parfaite, j'ai essayé votre code et j'ai remarqué qu'il y a de l'espace devant le bouton de retour même si vous avez ajouté un bouton de barre avec une largeur négative.
Pawriwes
26

Si vous souhaitez avoir un bouton de retour avec une flèche de retour, vous pouvez utiliser une image et un code ci-dessous

backArrow.png flèche1[email protected] flèche2[email protected]flèche3

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
la source
12

Si vous utilisez, navigationControllerajoutez le UINavigationControllerDelegateprotocole à la classe et ajoutez la méthode déléguée comme suit:

class ViewController:UINavigationControllerDelegate {

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

Cette méthode est appelée chaque fois que le contrôleur de navigation glisse vers un nouvel écran. Si le bouton de retour a été enfoncé, le nouveau contrôleur de vue est ViewControllerlui - même.

Ajinkya Patil
la source
Ce qui devient horrible lors de l'utilisation d'une classe non NSObjectProtocol en tant que délégué.
Nick Weaver
Il n'est pas toujours appelé lorsque vous appuyez sur le bouton de retour.
Ted
9

Dans Swift 5 et Xcode 10.2

Veuillez ne pas ajouter d'élément de bouton de barre personnalisé, utilisez ce comportement par défaut.

Pas besoin de vueWillDisappear , pas besoin de BarButtonItem personnalisé etc ...

Il vaut mieux détecter quand le VC est supprimé de son parent.

Utilisez l'une de ces deux fonctions

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

Si vous souhaitez arrêter le comportement par défaut du bouton Précédent, ajoutez ensuite BarButtonItem personnalisé.

iOS
la source
1
Notez que cela est également appelé lorsque vous sautez par programme, non seulement appuyez sur le bouton de retour.
Ixx
7

NON

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

Cela sera appelé même si vous vous connectez au contrôleur de vue dans lequel vous remplacez cette méthode. Dans quel cas vérifier si le " parent" n'est nilpas n'est pas un moyen précis pour être sûr de revenir au bon UIViewController. Pour déterminer exactement si le UINavigationControllernavigateur retourne correctement à celui UIViewControllerqui a présenté celui-ci, vous devrez vous conformer auUINavigationControllerDelegate protocole.

OUI

note: MyViewControllerc'est juste le nom de tout UIViewControllerce que vous voulez détecter en remontant.

1) En haut de votre fichier, ajoutez UINavigationControllerDelegate.

class MyViewController: UIViewController, UINavigationControllerDelegate {

2) Ajoutez une propriété à votre classe qui gardera une trace de UIViewControllercelle dont vous faites la succession.

class MyViewController: UIViewController, UINavigationControllerDelegate {

var previousViewController:UIViewController

3) Dans MyViewControllerla viewDidLoadméthode de, attribuez le selfrôle de délégué à votre UINavigationController.

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

3) Avant de vous enchaîner , affectez le précédent UIViewControllercomme propriété.

// 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) Et conforme à une méthode MyViewControllerduUINavigationControllerDelegate

func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
    if viewController == self.previousViewController {
        // You are going back
    }
}
Brandon A
la source
1
Merci pour la réponse utile! Les lecteurs se méfient de définir le délégué UINavigationController sur un contrôleur de vue spécifique; si le contrôleur de navigation a déjà un délégué, vous courez le risque de priver l'autre délégué des rappels qu'il attend. Dans notre application, le délégué UINavigationController est un objet partagé (un AppCoordinator) vers lequel tous les contrôleurs de vue ont un pointeur.
Bill Feth
7

Dans mon cas, le viewWillDisappearmieux a fonctionné. Mais dans certains cas, il faut modifier le contrôleur de vue précédent. Voici donc ma solution avec l'accès au contrôleur de vue précédent et cela fonctionne dans 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
la source
-viewDidDisappear (ou -viewWillDisappear) sera appelé même si la vue est couverte par la vue d'un autre contrôleur de vue (pas seulement lorsque vous appuyez sur le bouton <Retour), d'où la nécessité de vérifier isMovingFromParentViewController.
Bill Feth
5

Avant de quitter le contrôleur actuel, je dois montrer une alerte. Je l'ai donc fait de cette façon:

  1. Ajouter une extension à UINavigationControlleravecUINavigationBarDelegate
  2. Ajouter un sélecteur à la navigation de votre contrôleurShouldPopOnBack (achèvement :)

Ça a marché)

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
la source
4

Ce n'est pas difficile comme nous le pensons. Créez simplement un cadre pour UIButton avec une couleur d'arrière-plan claire, attribuez une action au bouton et placez-le sur le bouton de retour de la barre de navigation. Et enfin, retirez le bouton après utilisation.

Voici l'exemple de code Swift 3 fait avec UIImage au lieu de 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)
    }

écrire le code doit être exécuté

func back(sender: UIBarButtonItem) {

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

    }

Supprimer la sous-vue une fois l'action effectuée

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

    for view in (self.navigationController?.navigationBar.subviews)!{
        if view.tag == 1 {
            view.removeFromSuperview()
        }
    }
ARSHWIN DENUEV LAL
la source
Merci mec . :-)
ARSHWIN DENUEV LAL
Comment créez-vous un état au toucher?
quang thang
Cela ne semble pas fonctionner dans iOS 11. Pas lorsque la couleur d'arrière-plan de UIImageView est claire. Réglez-le sur une couleur différente et cela fonctionne.
Tapez Forms
Nous pouvons définir une UIImageView avec des couleurs claires, définir son cadre, attribuer une tap tapure et placer n'importe où sur l'écran. Alors pourquoi ne pouvons-nous pas le placer sur une barre de navigation. Pour être sincère, je ne recommanderai pas ce que j'ai écrit. S'il y a un problème, il y a certainement une raison, mais ce n'est pas la couleur qui compte. Oubliez le code suivez la logique u réussira. :)
ARSHWIN DENUEV LAL
4

Swift 4.2:

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

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

    }
}
Md. Najmul Hasan
la source
3

Swift 3:

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

    if parent == nil{
        print("Back button was clicked")
    }
}
Fille
la source
-did / willMove (toParentViewController :) est peut-être mieux que de vérifier isMovingTfromParentViewController dans -viewWillDisappear car il n'est appelé que lorsque le contrôleur de vue change réellement de parent (pas lorsque la vue est couverte par la vue d'un autre VC) Mais la solution la plus "correcte" est pour implémenter la méthode déléguée UINavigationController. Soyez prudent, cependant; si le NavigationController a déjà un délégué, vous courez le risque de priver l'autre délégué des rappels qu'il attend.
Bill Feth
J'ai testé avec un splitViewController. Là, ne pouvait pas faire la différence entre ajouté ou supprimé.
claude31
2

Essaye ça .

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

Essayez aussi.

override func viewWillAppear(animated: Bool) {
  //empty your array
}
AG
la source
2

il suffit de contrôler + de faire glisser l'élément de la barre en dessous de func. travailler comme un charme

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

entrez la description de l'image ici

codeurs
la source
2

Vous pouvez sous UINavigationController-classer et remplacer popViewController(animated: Bool). En plus de pouvoir y exécuter du code, vous pouvez également empêcher l'utilisateur de revenir en arrière, par exemple pour demander d'enregistrer ou de supprimer son travail actuel.

Exemple d'implémentation où vous pouvez définir un popHandlerqui est défini / effacé par les contrôleurs poussés.

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

Et un exemple d'utilisation à partir d'un contrôleur poussé qui suit le travail non enregistré.

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

Comme touche agréable, cela sera également appelé interactivePopGestureRecognizerlorsque l'utilisateur essaiera de revenir en arrière en faisant un geste de balayage.

Rivera
la source
réponse supérieure, merci Rivera
DvixExtract
2

C'est ma solution

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

Dans votre contrôleur de vue, vous pouvez gérer comme ceci:

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

Si je comprends bien , vous voulez vider votre arrayque vous appuyez sur le bouton de retour et de la pop à votre précédente ViewController letvotre Arrayqui vous avez chargé sur cet écran est

let settingArray  = NSMutableArray()
@IBAction func Back(sender: AnyObject) {
    self. settingArray.removeAllObjects()
    self.dismissViewControllerAnimated(true, completion: nil)
} 
Avinash Mishra
la source
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
la source
1
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    if self.isMovingToParent {

        //your code backView
    }
}
Papon Smc
la source
1

Pour Swift 5 , nous pouvons vérifier que la vue disparaîtra

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

    if self.isMovingFromParent {
        delegate?.passValue(clickedImage: selectedImage)
    }
}
Sandu
la source
1

Swift 5 __ Xcode 11.5

Dans mon cas, je voulais faire une animation, et quand elle a fini, revenir en arrière. Un moyen d'écraser l'action par défaut du bouton de retour et d'appeler votre action personnalisée est le suivant:

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

NavigationBar hiérarchie des vues

Ou vous pouvez utiliser cette méthode une fois pour explorer la hiérarchie des vues NavigationBar et obtenir les index pour accéder à la vue _UIButtonBarButton, transtyper en UIControl, supprimer l'action-cible et ajouter vos actions-cibles personnalisées:

    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
la source
0

J'ai accompli cela en appelant / en remplaçant viewWillDisappearpuis en accédant à la pile de ce qui navigationControllersuit:

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
la source
0

Voilà comment je l'ai résolu pour mon propre problème

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
la source
0

Voici la solution Swift 5 la plus simple possible qui ne vous oblige pas à créer un bouton de retour personnalisé et à renoncer à toutes les fonctionnalités du bouton gauche UINavigationController que vous obtenez gratuitement.

Comme Brandon A le recommande ci-dessus, vous devez implémenter UINavigationControllerDelegatedans le contrôleur de vue avec lequel vous souhaitez interagir avant d'y revenir. Un bon moyen consiste à créer une séquence de déroulement que vous pouvez effectuer manuellement ou automatiquement et réutiliser le même code à partir d'un bouton personnalisé ou du bouton précédent.

Tout d'abord, faites de votre contrôleur de vue d'intérêt (celui que vous souhaitez détecter le retour) un délégué du contrôleur de navigation dans son viewDidLoad:

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

Deuxièmement, ajoutez une extension au bas du fichier qui remplace 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)
            }
        }
    }
}

Étant donné que votre question comprenait un UITableViewController, j'ai inclus un moyen d'obtenir le chemin d'index de la ligne que l'utilisateur a tapée.

John Pavley
la source