Eseguire l'azione quando si preme il pulsante della barra posteriore di UINavigationController


204

Devo eseguire un'azione (svuotare un array), quando UINavigationControllerviene premuto il pulsante back di a , mentre il pulsante fa ancora ViewControllerapparire il precedente nello stack. Come potrei ottenere ciò usando swift? inserisci qui la descrizione dell'immagine

Risposte:


152

Un'opzione sarebbe implementare il tuo pulsante indietro personalizzato. Dovresti aggiungere il seguente codice al tuo metodo 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];
}

AGGIORNARE:

Ecco la versione di 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)
    }

AGGIORNAMENTO 2:

Ecco la versione di 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
Questo non viene visualizzato al controller della vista precedente; si apre al controller della vista principale.
roccioso

91
Come posso avere una freccia come un normale pulsante Indietro?
TomSawyer,

@rocky È possibile provare la riga seguente nella funzione back: [self.navigationController dismissViewControllerAnimated: YES completamento: zero];
malajisi,

2
@TomSawyer Per questo, dai un'occhiata alla risposta qui sotto
fr33g

7
Fare una sostituzione di un pulsante di sistema per sovrascrivere una funzione non è un buon modo. Il modo migliore è la risposta qui sotto! stackoverflow.com/a/27715660/2307276
dpizzuto

478

Sostituire il pulsante con uno personalizzato come suggerito in un'altra risposta non è probabilmente una grande idea in quanto perderai il comportamento e lo stile predefiniti.

Un'altra opzione che hai è implementare il metodo viewWillDisappear sul View Controller e verificare la presenza di una proprietà denominata isMovingFromParentViewController . Se quella proprietà è vera, significa che View Controller sta scomparendo perché viene rimosso (spuntato).

Dovrebbe assomigliare a:

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

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

In rapido 4.2

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

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

5
@gmogames sì, non puoi farlo. La domanda non lo ha fatto però. Per riuscire a fermare l'azione di tornare indietro, immagino che tu abbia davvero bisogno di scavalcare il pulsante.
Manecosta,

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

23
viewWillDisappear(animated:)verrà attivato se si riceve una telefonata. Questo probabilmente non è quello che vuoi. Probabilmente meglio usarewillMove(toParentViewController:)
Joe Susnick il

in Swift 4, mancante : override func viewWillDisappear ( animato: Bool)
Javier Calatrava Llavería

1
Questa dovrebbe essere la risposta accettata. Pulito e semplice.
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
Non funzionando in Swift3 / iOS10, la console stampa "l'animazione pop nidificata può causare la barra di navigazione danneggiata".
itsji10dra,

1
Non essere chiamato affatto
zulkarnain shah,

3
Questo viene anche chiamato quando ci si sposta in un nuovo VC, non solo quando si torna indietro.
Jose Ramirez,

Come da commento di @JozemiteApps, è nei documenti Chiamati appena prima che il controller di visualizzazione venga aggiunto o rimosso da un controller di visualizzazione contenitore. .
nstein,

2
Questa dovrebbe essere la risposta accettata. E quando parent == nilè quando ci stiamo muovendo di nuovo alla parentscena
Sylvan D Ash

32

Sono stato in grado di raggiungere questo obiettivo con quanto segue:

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

Non è necessario il pulsante Indietro personalizzato.


Questo è fantastico. Vecchia osservazione ma funziona ancora con l'ultimo Swift.
user3204765

Questo viene attivato (falso positivo) anche quando si svolge dal controller della vista successiva (su questo), quindi non si rileva realmente la pressione del pulsante indietro.
user2878850,

Stessa osservazione della persona precedente, questo codice non rileva l'attivazione del pulsante Indietro, ma il pop della vista corrente.
Vilmir,

31

Ho creato questa classe (rapida) per creare un pulsante Indietro esattamente come quello normale, inclusa la freccia indietro. Può creare un pulsante con testo normale o con un'immagine.

uso

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

(codice per disegnare la freccia indietro creata con il 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
        }
    }
}

Saresti così gentile da aggiornare la tua risposta per iOS 11?
BR41N-FCK,

2
Ciao @guido, la tua soluzione è perfetta, ho provato il tuo codice e ho notato che c'è spazio davanti al pulsante Indietro anche se hai aggiunto un pulsante con larghezza negativa.
Pawriwes

26

Se si desidera avere il pulsante Indietro con la freccia indietro, è possibile utilizzare un'immagine e un codice di seguito

backArrow.png arrow1backArrow@2x.png arrow2backArrow@3x.pngarrow3

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

Se si utilizza, navigationControlleraggiungere il UINavigationControllerDelegateprotocollo alla classe e aggiungere il metodo delegato come segue:

class ViewController:UINavigationControllerDelegate {

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

Questo metodo viene chiamato ogni volta che il controller di navigazione passa a una nuova schermata. Se è stato premuto il pulsante Indietro, il nuovo controller di visualizzazione è esso ViewControllerstesso.


Il che diventa orribile quando si utilizza una classe non NSObjectProtocol come delegato.
Nick Weaver

Non viene sempre chiamato quando si preme il pulsante Indietro.
Ted,

9

In Swift 5 e Xcode 10.2

Non aggiungere elementi del pulsante della barra personalizzata, utilizzare questo comportamento predefinito.

Nessuna necessità di visualizzazione Scompare , nessuna necessità di BarButtonItem personalizzato ecc ...

È meglio rilevare quando il VC viene rimosso dal suo genitore.

Utilizzare una di queste due funzioni

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

Se si desidera interrompere il comportamento predefinito del pulsante Indietro, aggiungere BarButtonItem personalizzato.


1
Nota che questo è anche chiamato quando fai pop programmaticamente, non solo premi il pulsante Indietro.
Ixx,

7

NO

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

Questo verrà chiamato anche se si sta lavorando poi per il controller della vista in cui stai facendo override di questo metodo. In che modo " parent" nilnon è, non è un modo preciso per essere sicuri di tornare al corretto UIViewController. Per determinare esattamente se UINavigationControllersta tornando correttamente a quello UIViewControllerche ha presentato questo attuale, dovrai conformarti al UINavigationControllerDelegateprotocollo.

nota: MyViewControllerè solo il nome di tutto ciò che UIViewControllersi desidera rilevare tornando indietro.

1) Nella parte superiore del file aggiungere UINavigationControllerDelegate.

class MyViewController: UIViewController, UINavigationControllerDelegate {

2) Aggiungi una proprietà alla tua classe che terrà traccia di UIViewControllerciò che stai seguendo.

class MyViewController: UIViewController, UINavigationControllerDelegate {

var previousViewController:UIViewController

3) in MyViewController's viewDidLoadmetodo di assegnare selfcome il delegato per la UINavigationController.

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

3) Prima di seguire , assegnare il precedente UIViewControllera questa proprietà.

// 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) E conformarsi a un metodo in MyViewControllerdellaUINavigationControllerDelegate

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

1
Grazie per la risposta utile! I lettori devono fare attenzione a impostare il delegato di UINavigationController su un controller di vista specifico; se il controller di navigazione ha già un delegato, si corre il rischio di privare l'altro delegato dei callback che si aspetta. Nella nostra app, il delegato di UINavigationController è un oggetto condiviso (un AppCoordinator) a cui tutti i controller di visualizzazione hanno un puntatore.
Bill Feth,

7

Nel mio caso ha viewWillDisappearfunzionato meglio. Ma in alcuni casi è necessario modificare il controller della vista precedente. Quindi ecco la mia soluzione con accesso al controller della vista precedente e funziona in 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()
                }
            }
        }
    }

-viewDidDisappear (o -viewWillDisappear) verrà chiamato anche se la vista è coperta da un'altra vista del controller della vista (non solo quando si preme il pulsante <Indietro), quindi è necessario verificare isMovingFromParentViewController.
Bill Feth,

5

Prima di lasciare il controller corrente devo mostrare un avviso. Quindi l'ho fatto in questo modo:

  1. Aggiungi estensione a UINavigationControllerconUINavigationBarDelegate
  2. Aggiungi selettore alla navigazione del tuo controllerShouldPopOnBack (completamento :)

Ha funzionato)

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

Non è difficile come noi. Basta creare una cornice per UIButton con un colore di sfondo chiaro, assegnare un'azione per il pulsante e posizionarla sul pulsante Indietro della barra di navigazione. E infine rimuovere il pulsante dopo l'uso.

Ecco il codice di esempio di Swift 3 fatto con UIImage invece di 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)
    }

scrivere il codice deve essere eseguito

func back(sender: UIBarButtonItem) {

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

    }

Rimuovere il subView dopo l'esecuzione dell'azione

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

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

Grazie amico. :-)
ARSHWIN DENUEV LAL

Come si crea lo stato quando si tocca?
Quang Thang

Questo non sembra funzionare in iOS 11. Non quando il colore di sfondo di UIImageView è chiaro. Impostalo su un colore diverso e funziona.
Toccare Forms

Siamo in grado di definire un UIImageView con colori chiari, impostare la sua cornice, assegnare tapgesture e posizionarlo ovunque sullo schermo. Quindi perché non possiamo posizionarlo su una barra di navigazione. Ad essere sincero, non consiglierò ciò che ho scritto. Se c'è un problema sicuramente c'è un motivo ma non è il colore che conta. Dimentica il codice, segui la logica che riuscirai. :)
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 :) è probabilmente meglio che controllare isMovingTfromParentViewController in -viewWillDisappear poiché viene chiamato solo quando il controller della vista sta effettivamente cambiando i genitori (non quando la vista è coperta dalla vista di un altro VC) Ma la soluzione più "corretta" è per implementare il metodo delegato UINavigationController. Stai attento, però; se NavigationController ha già un delegato, si corre il rischio di privare quell'altro delegato di richiamate che si aspetta.
Bill Feth,

Ho provato con un splitViewController. Lì, non è stato possibile fare la differenza tra aggiunto o rimosso.
claude31,

2

Prova questo .

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

Prova anche questo.

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

2

basta controllare + trascinare l'elemento della barra nella seguente funzione. lavorare come un incantesimo

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

inserisci qui la descrizione dell'immagine


2

È possibile UINavigationControllereseguire la sottoclasse e l'override popViewController(animated: Bool). Oltre a poter eseguire un po 'di codice lì, puoi anche impedire all'utente di tornare indietro del tutto, ad esempio per chiedere di salvare o scartare il suo lavoro attuale.

Implementazione di esempio in cui è possibile impostare un valore popHandlerche viene impostato / cancellato dai controller push.

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

E prova l'utilizzo da un controller push che tiene traccia del lavoro non salvato.

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

Come un bel tocco, questo verrà anche chiamato da interactivePopGestureRecognizerquando l'utente tenta di tornare indietro con un gesto di scorrimento.


risposta superiore, grazie Rivera
DvixExtract

2

Questa è la mia soluzione

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

Nel controller della vista, puoi gestire in questo modo:

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

1

A quanto ho capito, vuoi svuotare il tuo arraymentre premi il pulsante Indietro e fai apparire ViewController letil tuo precedente Arrayche hai caricato su questa schermata

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

Per Swift 5 , possiamo verificare che scompare

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

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

1

Swift 5 __ Xcode 11.5

Nel mio caso volevo realizzare un'animazione e, una volta terminato, tornare indietro. Un modo per sovrascrivere l'azione predefinita del pulsante Indietro e chiamare l'azione personalizzata è questo:

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

Gerarchia di visualizzazione NavigationBar

Oppure puoi utilizzare questo metodo una volta per esplorare la gerarchia della vista NavigationBar e ottenere gli indici per accedere alla vista _UIButtonBarButton, eseguire il cast su UIControl, rimuovere l'azione target e aggiungere le azioni target personalizzate:

    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

Ho realizzato questo chiamando / sovrascrivendo viewWillDisappeare quindi accedendo allo stack navigationControllercome questo:

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

È così che l'ho risolto per il mio problema

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

Ecco la soluzione Swift 5 più semplice possibile che non richiede di creare un pulsante Indietro personalizzato e rinunciare a tutte le funzionalità del pulsante sinistro di UINavigationController che ottieni gratuitamente.

Come consigliato da Brandon A , è necessario implementare UINavigationControllerDelegatenel controller di visualizzazione con cui si desidera interagire prima di tornare ad esso. Un buon modo è quello di creare un seguito di svolgimento che è possibile eseguire manualmente o automaticamente e riutilizzare lo stesso codice da un pulsante personalizzato o dal pulsante Indietro.

Innanzitutto, rendi il tuo controller di visualizzazione di interesse (quello a cui desideri rilevare il ritorno) un delegato del controller di navigazione nel suo viewDidLoad:

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

In secondo luogo, aggiungere un'estensione nella parte inferiore del file che sovrascrive 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)
            }
        }
    }
}

Poiché la tua domanda includeva a UITableViewController, ho incluso un modo per ottenere il percorso dell'indice della riga che l'utente ha toccato.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.