Налаштування дії кнопки "назад" в навігаційному контролері


180

Я намагаюся перезаписати дію за замовчуванням кнопки "назад" у навігаційному контролері. Я надав цільову дію на спеціальній кнопці. Дивна річ - це при призначенні її, хоча атрибут backbutton не звертає на них уваги, і він просто вискакує поточний вигляд і повертається до кореня:

UIBarButtonItem *backButton = [[UIBarButtonItem alloc] 
                                  initWithTitle: @"Servers" 
                                  style:UIBarButtonItemStylePlain 
                                  target:self 
                                  action:@selector(home)];
self.navigationItem.backBarButtonItem = backButton;

Як тільки я встановив його через функцію leftBarButtonItemon, navigationItemвона викликає мою дію, проте тоді кнопка виглядає як звичайна кругла, а не стрілка назад:

self.navigationItem.leftBarButtonItem = backButton;

Як я можу змусити його викликати власні дії перед поверненням до кореневого виду? Чи є спосіб перезаписати зворотну дію за замовчуванням, чи існує метод, який завжди викликається, залишаючи представлення ( viewDidUnloadне робить цього)?


дія: @selector (домашня)]; потребує: після дії селектора: @selector (home :)]; інакше це не спрацює
PartySoft

7
@PartySoft Це неправда, якщо метод не оголошений двокрапкою. Цілком справедливо мати кнопки вибору дзвінків, які не приймають жодних параметрів.
mbm29414


3
Чому Apple не надасть кнопку в стилі, схожому на кнопку назад? Здається, досить очевидно.
ДжонК

Відповіді:


363

Спробуйте помістити це в контролер перегляду, де ви хочете виявити пресу:

-(void) viewWillDisappear:(BOOL)animated {
    if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound) {
       // back button was pressed.  We know this is true because self is no longer
       // in the navigation stack.  
    }
    [super viewWillDisappear:animated];
}

1
це гладкий, чистий, приємний і дуже добре продуманий спосіб вирішення
boliva

6
+1 чудовий хак, але не пропонує контролю над анімацією поп
матму

3
Не працює для мене, якщо я надсилаю повідомлення делегату за допомогою кнопки і делегат вискакує контролер - це все ще спрацьовує.
SAHM

21
Ще одна проблема полягає в тому, що ви не можете розрізнити, чи користувач натиснув кнопку "назад" або якщо ви запросили програмно [self.navigationController popViewControllerAnimated: ТАК]
Чейз Робертс

10
Просто версія FYI: Swift:if (find(self.navigationController!.viewControllers as! [UIViewController],self)==nil)
hEADcRASH

177

Я реалізував розширення UIViewController-BackButtonHandler . Для цього не потрібно нічого підкласифікувати, просто введіть його у свій проект та замініть navigationShouldPopOnBackButtonметод у UIViewControllerкласі:

-(BOOL) navigationShouldPopOnBackButton {
    if(needsShowConfirmation) {
        // Show confirmation alert
        // ...
        return NO; // Ignore 'Back' button this time
    }
    return YES; // Process 'Back' button click and Pop view controler
}

Завантажте зразок програми .


16
Це найчистіше рішення, яке я бачив, краще і простіше, ніж використання власного користувальницького UIButton. Дякую!
ramirogm

4
Це navigationBar:shouldPopItem:не приватний метод, оскільки він є частиною UINavigationBarDelegateпротоколу.
onegray

1
але чи UINavigationController вже реалізує делегата (для повернення YES)? чи це буде в майбутньому? підкласифікація - це, мабуть, більш безпечний варіант
Сем

8
Я щойно реалізував це (досить крутий BTW) в iOS 7.1 і зауважив, що після повернення NOкнопка повернення залишається у відключеному стані (візуально, оскільки вона все ще приймає та реагує на події на дотик). Я працював над цим, додаючи заявку elseдо shouldPopперевірки та проїжджаючи черезalpha
підпогляди

2
Це одне з найкращих розширень, які я коли-небудь бачив. Дуже дякую.
Шрікант

42

На відміну від сказаного Amagrammer, це можливо. Ви повинні підкласифікувати своє navigationController. Я тут все пояснив (включаючи код прикладу).


Документація Apple ( developer.apple.com/iphone/library/documentation/UIKit/… ) говорить, що "Цей клас не призначений для підкласифікації". Хоча я не впевнений, що вони мають на увазі під цим - вони можуть означати "вам зазвичай не потрібно цього робити", або вони можуть означати "ми відхилимо вашу програму, якщо ви поспішаєте з нашим контролером" ...
Kuba Suder

Це, безумовно, єдиний спосіб зробити це. Бажаю, я міг би надати вам більше балів Ганс!
Адам Ебербах

1
Чи можете ви фактично запобігти виходу з представлення даних за допомогою цього методу? Що б ви змусили повернути метод popViewControllerAnimated, якщо ви хочете, щоб представлення не виходило?
ДжозефH

1
Так, можна. Просто не закликайте метод суперкласу у своїй реалізації, пам’ятайте! Не варто цього робити, користувач розраховує повернутися в навігацію. Що ви можете зробити, це попросити підтвердження. Згідно з документацією Apple, popViewController повертає: "Контролер перегляду, який вискочив із стека." Тож коли нічого не вискочить, ви повинні повернути нуль;
HansPinckaers

1
@HansPickaers Я думаю, що ваша відповідь щодо запобігання виходу з подання може бути дещо помилковою. Якщо я відображую повідомлення «підтвердження» від реалізації підкласів popViewControllerAnimated :, НавігаціяBar все одно оживляє один рівень дерева, незалежно від того, що я повертаю. Це, мабуть, тому, що натисканням кнопки "Назад" дзвонить слідPopNavigationItem на панелі навігації. Повертаю нуль зі свого методу підкласів, як рекомендовано.
deepwinter

15

Швидка версія:

(про https://stackoverflow.com/a/19132881/826435 )

У вашому контролері подання ви просто дотримуєтесь протоколу і виконайте всі необхідні дії:

extension MyViewController: NavigationControllerBackButtonDelegate {
    func shouldPopOnBackButtonPress() -> Bool {
        performSomeActionOnThePressOfABackButton()
        return false
    }
}

Потім створіть клас, скажімо NavigationController+BackButton, і просто скопіюйте вставший код нижче:

protocol NavigationControllerBackButtonDelegate {
    func shouldPopOnBackButtonPress() -> Bool
}

extension UINavigationController {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        // Prevents from a synchronization issue of popping too many navigation items
        // and not enough view controllers or viceversa from unusual tapping
        if viewControllers.count < navigationBar.items!.count {
            return true
        }

        // Check if we have a view controller that wants to respond to being popped
        var shouldPop = true
        if let viewController = topViewController as? NavigationControllerBackButtonDelegate {
            shouldPop = viewController.shouldPopOnBackButtonPress()
        }

        if (shouldPop) {
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        } else {
            // Prevent the back button from staying in an disabled state
            for view in navigationBar.subviews {
                if view.alpha < 1.0 {
                    UIView.animate(withDuration: 0.25, animations: {
                        view.alpha = 1.0
                    })
                }
            }

        }

        return false
    }
}

Можливо, я щось пропустив, але це не працює для мене, метод виконуватиSomeActionOnThePressOfABackButton розширення ніколи не називається
Turvy

@FlorentBreton можливо непорозуміння? shouldPopOnBackButtonPressслід телефонувати, поки немає помилок. performSomeActionOnThePressOfABackButtonце просто сформований метод, який не існує.
kgaidis

Я зрозумів це, тому я створив performSomeActionOnThePressOfABackButtonу своєму контролері метод виконання певної дії при натисканні кнопки "назад", але цей метод ніколи не викликався, дія - це нормальне повернення назад
Turvy

1
Не працює і для мене. Метод ShouldPop ніколи не викликається. Ви десь встановили делегата?
Тім Атін

@TimAutin Я просто перевірив це ще раз і, здається, щось змінилося. Ключова частина для розуміння того, що в а UINavigationController, navigationBar.delegateвстановлено навігаційний контролер. Тож методам ДОЛЖНЕ називатись. Однак у Swift я не можу змусити їх називатися навіть у підкласі. Однак я змусив їх називатися в Objective-C, тому зараз просто використовую версію Objective-C. Це може бути помилка Swift.
kgaidis

5

Це неможливо зробити безпосередньо. Є кілька альтернатив:

  1. Створіть свій власний звичай, UIBarButtonItemякий підтверджує, коли тест пройде
  2. Перевірте вміст поля форми за допомогою UITextFieldметоду делегата, наприклад -textFieldShouldReturn:, який викликається після натискання кнопки Returnабо Doneна клавіатурі

Недоліком першого варіанту є те, що стиль лівої вказівної стрілки кнопки назад не може бути доступний за допомогою спеціальної кнопки панелі. Тож вам доведеться використовувати зображення або перейти за допомогою звичайної кнопки стилю.

Другий варіант хороший тим, що ви повертаєте текстове поле назад методом делегування, тому ви можете орієнтувати свою логіку перевірки на конкретне текстове поле, надіслане методу зворотного виклику делегата.


5

З певних причин нарізання рішення, яке згадував @HansPinckaers, не було для мене правильним, але я знайшов дуже простий спосіб зачепити натиснуту кнопку назад, і хочу зафіксувати це, якщо це може уникнути години обману хтось інший. Трюк дійсно простий: просто додайте прозорий UIButton як підвідмову до свого UINavigationBar і встановіть для нього вибрані вами вибрані так, як ніби це справжня кнопка! Ось приклад використання Monotouch та C #, але переклад на aim-c не повинен бути надто важким.

public class Test : UIViewController {
    public override void ViewDidLoad() {
        UIButton b = new UIButton(new RectangleF(0, 0, 60, 44)); //width must be adapted to label contained in button
        b.BackgroundColor = UIColor.Clear; //making the background invisible
        b.Title = string.Empty; // and no need to write anything
        b.TouchDown += delegate {
            Console.WriteLine("caught!");
            if (true) // check what you want here
                NavigationController.PopViewControllerAnimated(true); // and then we pop if we want
        };
        NavigationController.NavigationBar.AddSubview(button); // insert the button to the nav bar
    }
}

Забавний факт: для тестування та пошуку хороших розмірів для моєї підробленої кнопки я встановив її колір тла на синій ... І це відображається за задньою кнопкою! У будь-якому випадку, він все ще вловлює будь-який дотик, націлений на оригінальну кнопку.


3

Ця методика дозволяє змінювати текст кнопки «назад», не впливаючи на заголовок будь-якого з контролерів перегляду чи бачачи зміну тексту задньої кнопки під час анімації.

Додайте це до методу init у контролері подання виклику :

UIBarButtonItem *temporaryBarButtonItem = [[UIBarButtonItem alloc] init];   
temporaryBarButtonItem.title = @"Back";
self.navigationItem.backBarButtonItem = temporaryBarButtonItem;
[temporaryBarButtonItem release];

3

Найпростіший спосіб

Ви можете використовувати делегатні методи UINavigationController. Метод willShowViewControllerвикликається при натисканні кнопки "назад" вашого VC. Робити все, що ви хочете, коли натиснути назад btn

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;

Переконайтесь, що ваш контролер представлення даних встановлюється як делегат спадкового навігаційного контролера та відповідає протоколу UINavigationControllerDelegate
Джастін Міло,

3

Ось моє рішення Swift. У своєму підкласі UIViewController замініть метод navigationShouldPopOnBackButton.

extension UIViewController {
    func navigationShouldPopOnBackButton() -> Bool {
        return true
    }
}

extension UINavigationController {

    func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool {
        if let vc = self.topViewController {
            if vc.navigationShouldPopOnBackButton() {
                self.popViewControllerAnimated(true)
            } else {
                for it in navigationBar.subviews {
                    let view = it as! UIView
                    if view.alpha < 1.0 {
                        [UIView .animateWithDuration(0.25, animations: { () -> Void in
                            view.alpha = 1.0
                        })]
                    }
                }
                return false
            }
        }
        return true
    }

}

Перевизначення методу навігаціїShouldPopOnBackButton в UIViewController не працює - Програма виконує батьківський метод, не переопределений. Будь-яке рішення для цього? Хтось має таке ж питання?
Pawel Cala

вона повернеться до rootview, якщо повернеться правдою
Pawriwes

@Pawriwes Ось рішення , яке я написав , що , здається, працює для мене: stackoverflow.com/a/34343418/826435
kgaidis

3

Знайдено рішення, яке також зберігає стиль кнопки "Назад". Додайте наступний метод до свого контролера перегляду.

-(void) overrideBack{

    UIButton *transparentButton = [[UIButton alloc] init];
    [transparentButton setFrame:CGRectMake(0,0, 50, 40)];
    [transparentButton setBackgroundColor:[UIColor clearColor]];
    [transparentButton addTarget:self action:@selector(backAction:) forControlEvents:UIControlEventTouchUpInside];
    [self.navigationController.navigationBar addSubview:transparentButton];


}

Тепер надайте функціонал за потребою в наступному методі:

-(void)backAction:(UIBarButtonItem *)sender {
    //Your functionality
}

Все, що вона робить - це прикрити кнопку назад за допомогою прозорої кнопки;)


3

Переосмислення навігаціїBar (_ navigationBar: shouldPop) : Це не дуже гарна ідея, навіть якщо вона працює. для мене це призвело до випадкових збоїв при навігації назад. Я раджу вам просто змінити кнопку "назад", видаливши типову кнопку назад за допомогою навігаційного елемента та створивши спеціальну кнопку "назад", як показано нижче:

override func viewDidLoad(){
   super.viewDidLoad()
   
   navigationItem.leftBarButton = .init(title: "Go Back", ... , action: #selector(myCutsomBackAction) 

   ...
 
}

==========================================

Спираючись на попередні відповіді с UIAlert в Swift5 в асинхронному способі


protocol NavigationControllerBackButtonDelegate {
    func shouldPopOnBackButtonPress(_ completion: @escaping (Bool) -> ())
}

extension UINavigationController: UINavigationBarDelegate {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
      
        if viewControllers.count < navigationBar.items!.count {
            return true
        }
        
        // Check if we have a view controller that wants to respond to being popped
        
        if let viewController = topViewController as? NavigationControllerBackButtonDelegate {
            
            viewController.shouldPopOnBackButtonPress { shouldPop in
                if (shouldPop) {
                    /// on confirm => pop
                    DispatchQueue.main.async {
                        self.popViewController(animated: true)
                    }
                } else {
                    /// on cancel => do nothing
                }
            }
            /// return false => so navigator will cancel the popBack
            /// until user confirm or cancel
            return false
        }else{
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        }
        return true
    }
}

На контролері


extension MyController: NavigationControllerBackButtonDelegate {
    
    func shouldPopOnBackButtonPress(_ completion: @escaping (Bool) -> ()) {
    
        let msg = "message"
        
        /// show UIAlert
        alertAttention(msg: msg, actions: [
            
            .init(title: "Continuer", style: .destructive, handler: { _ in
                completion(true)
            }),
            .init(title: "Annuler", style: .cancel, handler: { _ in
                completion(false)
            })
            ])
   
    }

}

Чи можете ви надати детальну інформацію про те, що відбувається з прапором if viewControllers.count <navigationBar.items! .Count {return true}?
H4Hugo

// Запобігає випуску синхронізації вискакування занадто багато елементів навігації // і недостатньо перегляду контролерів або viceversa від незвичного натискання
brahimm

2

Я не вірю, що це можливо, легко. Єдиний спосіб, як я вважаю, це зможе зробити - це створити власне зображення стрілки кнопки "Назад", щоб розмістити її там. Спочатку мені було страшно, але я бачу, чому, заради послідовності, це було залишено.

Ви можете наблизитись (без стрілки), створивши звичайну кнопку та приховавши кнопку повернення за замовчуванням:

self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:@"Servers" style:UIBarButtonItemStyleDone target:nil action:nil] autorelease];
self.navigationItem.hidesBackButton = YES;

2
Так, проблема полягає в тому, що я хочу, щоб це виглядало як звичайна кнопка "Назад", просто потрібно, щоб спочатку викликати мої спеціальні дії ...
Папуги

2

Там більш простий спосіб, просто спадкування методу делегата з UINavigationBarі перевизначити в ShouldPopItemметоді .


Я думаю, ви маєте на увазі сказати підклас класу UINavigationController і реалізувати метод shouldPopItem. Це добре працює для мене. Однак цей метод не повинен просто повернути ДА чи НІ так, як ви очікували. Пояснення і рішення є тут: stackoverflow.com/a/7453933/462162
arlomedia

2

Рішення onegray не є безпечним. Відповідно до офіційних документів Apple, https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html , ми повинні уникати цього.

"Якщо назва методу, оголошеного в категорії, збігається з методом у початковому класі або методом в іншій категорії того ж класу (або навіть надкласу), поведінка не визначена щодо того, який метод використання використовується під час виконання. Це менш ймовірно, що це проблема, якщо ви використовуєте категорії з власними класами, але це може спричинити проблеми при використанні категорій для додавання методів до стандартних класів какао чи какао сенсорного ".


2

Використання Swift:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    if self.navigationController?.topViewController != self {
        print("back button tapped")
    }
}

Що стосується iOS 10, а може і раніше, це вже не працює.
Мюррей Сагал

2

Ось версія Swift 3 відповіді @oneway на ловлю події кнопки повернення навігаційної панелі, перш ніж її звільнять. Як UINavigationBarDelegateне можна використовувати UIViewController, вам потрібно створити делегата, який буде спрацьовувати при navigationBar shouldPopвиклику.

@objc public protocol BackButtonDelegate {
      @objc optional func navigationShouldPopOnBackButton() -> Bool 
}

extension UINavigationController: UINavigationBarDelegate  {

    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {

        if viewControllers.count < (navigationBar.items?.count)! {                
            return true
        }

        var shouldPop = true
        let vc = self.topViewController

        if vc.responds(to: #selector(vc.navigationShouldPopOnBackButton)) {
            shouldPop = vc.navigationShouldPopOnBackButton()
        }

        if shouldPop {
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        } else {
            for subView in navigationBar.subviews {
                if(0 < subView.alpha && subView.alpha < 1) {
                    UIView.animate(withDuration: 0.25, animations: {
                        subView.alpha = 1
                    })
                }
            }
        }

        return false
    }
}

А потім у свій контролер подання додайте функцію делегата:

class BaseVC: UIViewController, BackButtonDelegate {
    func navigationShouldPopOnBackButton() -> Bool {
        if ... {
            return true
        } else {
            return false
        }        
    }
}

Я зрозумів, що ми часто хочемо додати контролер попередження для користувачів, щоб вирішити, чи хочуть вони повернутися. Якщо так, то ви завжди можете return falseв navigationShouldPopOnBackButton()функції і закрити контролер уявлення, роблячи що - щось на зразок цього:

func navigationShouldPopOnBackButton() -> Bool {
     let alert = UIAlertController(title: "Warning",
                                          message: "Do you want to quit?",
                                          preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { UIAlertAction in self.yes()}))
            alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: { UIAlertAction in self.no()}))
            present(alert, animated: true, completion: nil)
      return false
}

func yes() {
     print("yes")
     DispatchQueue.main.async {
            _ = self.navigationController?.popViewController(animated: true)
        }
}

func no() {
    print("no")       
}

Я отримую помилку: Value of type 'UIViewController' has no member 'navigationShouldPopOnBackButton' коли я намагаюся скласти ваш код, для рядка if vc.responds(to: #selector(v...Також self.topViewControllerповертається необов'язково, і для цього також є попередження.
Санкар

FWIW, я виправив цей код, зробивши: let vc = self.topViewController as! MyViewControllerі, здається, він працює добре. Якщо ви вважаєте, що це правильна зміна, ви можете змінити код. Також, якщо ви відчуваєте, що цього не слід робити, я буду радий дізнатися, чому. Дякуємо за цей код. Ви, ймовірно, повинні написати про це повідомлення в блозі, оскільки ця відповідь затаємлена відповідно до голосів.
Санкар

@SankarP Причина, з якою ви отримали цю помилку, - це, MyViewControllerможливо, не відповідає BackButtonDelegate. Замість того, щоб змушувати розмотувати, ви повинні робити, guard let vc = self.topViewController as? MyViewController else { return true }щоб уникнути можливих збоїв.
Lawliet

Дякую. Я думаю, що заява про захист має стати: guard let vc = self.topViewController as? MyViewController else { self.popViewController(animated: true) return true }переконатися, що екран переміщується на потрібну сторінку, якщо його неможливо правильно передати. Зараз я розумію, що navigationBarфункція викликається у всіх ВК, а не лише у контролері перегляду, де існує цей код. Можливо, буде добре також оновити код у своїй відповіді? Дякую.
Санкар

2

Swift 4 iOS 11.3 Версія:

Це ґрунтується на відповіді kgaidis з https://stackoverflow.com/a/34343418/4316579

Я не впевнений, коли розширення перестало працювати, але під час написання цього запиту (Swift 4) виявляється, що розширення більше не буде виконуватися, якщо ви не заявите відповідність UINavigationBarDelegate, як описано нижче.

Сподіваємось, це допомагає людям, які задаються питанням, чому їх розширення більше не працює.

extension UINavigationController: UINavigationBarDelegate {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {

    }
}

1

Використовуючи цільові та дії змінних, які ви зараз залишаєте на "нуль", ви повинні мати змогу передавати свої діалогові вікна збереження, щоб вони викликалися, коли кнопка "обрана". Слідкуйте, це може спрацьовувати в дивні моменти.

Я погоджуюся в основному з Amagrammer, але я не думаю, що зробити так, щоб кнопка була зроблена за допомогою стрілки. Я просто перейменував би кнопку назад, зробив знімок екрана, фотошоп потрібний розмір кнопки, і це зображення має у верхній частині вашої кнопки.


Я згоден, ви могли б зробити фотошоп, і я думаю, що я міг би зробити це, якби я дуже хотів цього, але тепер вирішив змінити вигляд і відчути крихітний шматочок, щоб зробити це так, як я хочу.
Джон Баллінгер

Так, за винятком того, що дії не спрацьовують, коли вони прикріплені до backBarButtonItem. Я не знаю, це помилка чи особливість; можливо, навіть Apple не знає. Що стосується вправи для фотошопу, я знову насторожуюся, що Apple відхилить додаток за неправомірне використання канонічного символу.
Amagrammer

Heads-up: цю відповідь було об'єднано з дубліката.
Shog9

1

Ви можете спробувати отримати доступ до елемента правої кнопки NavigationBars та встановити його властивість вибору ... ось ось посилання UIBarButtonItem , інша річ, якщо ця найдосконаліша робота, яка буде визначати роботу, - встановити потрібний елемент кнопки навігації на користувальницький UIBarButtonItem, який ви створити та встановити його селектор ... сподіваюся, це допоможе


Heads-up: цю відповідь було об'єднано з дубліката.
Shog9

1

Для такої форми, яка вимагає введення користувача таким чином, я рекомендую використовувати її як "модальний" замість частини вашого навігаційного стеку. Таким чином вони повинні піклуватися про бізнес на бланку, тоді ви можете підтвердити його та відхилити за допомогою спеціальної кнопки. Ви навіть можете спроектувати планку навігації, яка буде такою ж, як і у вашій програмі, але дає більше контролю.


Heads-up: цю відповідь було об'єднано з дубліката.
Shog9

1

Щоб перехопити кнопку "Назад", просто накрийте її прозорим UIControl і перехопіть дотики.

@interface MyViewController : UIViewController
{
    UIControl   *backCover;
    BOOL        inhibitBackButtonBOOL;
}
@end

@implementation MyViewController
-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    // Cover the back button (cannot do this in viewWillAppear -- too soon)
    if ( backCover == nil ) {
        backCover = [[UIControl alloc] initWithFrame:CGRectMake( 0, 0, 80, 44)];
#if TARGET_IPHONE_SIMULATOR
        // show the cover for testing
        backCover.backgroundColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.15];
#endif
        [backCover addTarget:self action:@selector(backCoverAction) forControlEvents:UIControlEventTouchDown];
        UINavigationBar *navBar = self.navigationController.navigationBar;
        [navBar addSubview:backCover];
    }
}

-(void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];

    [backCover removeFromSuperview];
    backCover = nil;
}

- (void)backCoverAction
{
    if ( inhibitBackButtonBOOL ) {
        NSLog(@"Back button aborted");
        // notify the user why...
    } else {
        [self.navigationController popViewControllerAnimated:YES]; // "Back"
    }
}
@end

Heads-up: цю відповідь було об'єднано з дубліката.
Shog9

1

Принаймні, у Xcode 5 є просте і досить хороше (не ідеальне) рішення. У IB перетягніть елемент панелі кнопки з панелі «Утиліти» та опустіть її в лівій частині панелі навігації, де була б кнопка «Назад». Встановіть мітку на "Назад". У вас буде функціонуюча кнопка, яку ви можете прив’язати до IBAction і закрити свій viewController. Я займаюся деякою роботою, а потім викликаю розмотуючий мозок, і він працює чудово.

Що не ідеально, це те, що ця кнопка не отримує стрілку <та не переносить попередній заголовок ДК, але я думаю, що цим можна керувати. Для своїх цілей я встановив, що нова кнопка "Назад" є кнопкою "Готово", щоб її ціль була зрозумілою.

У вас також є дві кнопки "Назад" у навігаторі IB, але позначити це для ясності досить просто.

введіть тут опис зображення


1

Швидкий

override func viewWillDisappear(animated: Bool) {
    let viewControllers = self.navigationController?.viewControllers!
    if indexOfArray(viewControllers!, searchObject: self) == nil {
        // do something
    }
    super.viewWillDisappear(animated)
}

func indexOfArray(array:[AnyObject], searchObject: AnyObject)-> Int? {
    for (index, value) in enumerate(array) {
        if value as UIViewController == searchObject as UIViewController {
            return index
        }
    }
    return nil
}

1

Такий підхід працював для мене (але кнопка "Назад" не матиме знаку "<"):

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIBarButtonItem* backNavButton = [[UIBarButtonItem alloc] initWithTitle:@"Back"
                                                                      style:UIBarButtonItemStyleBordered
                                                                     target:self
                                                                     action:@selector(backButtonClicked)];
    self.navigationItem.leftBarButtonItem = backNavButton;
}

-(void)backButtonClicked
{
    // Do something...
    AppDelegate* delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
    [delegate.navController popViewControllerAnimated:YES];
}

1

Швидка версія відповіді @ onegray

protocol RequestsNavigationPopVerification {
    var confirmationTitle: String { get }
    var confirmationMessage: String { get }
}

extension RequestsNavigationPopVerification where Self: UIViewController {
    var confirmationTitle: String {
        return "Go back?"
    }

    var confirmationMessage: String {
        return "Are you sure?"
    }
}

final class NavigationController: UINavigationController {

    func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool {

        guard let requestsPopConfirm = topViewController as? RequestsNavigationPopVerification else {
            popViewControllerAnimated(true)
            return true
        }

        let alertController = UIAlertController(title: requestsPopConfirm.confirmationTitle, message: requestsPopConfirm.confirmationMessage, preferredStyle: .Alert)

        alertController.addAction(UIAlertAction(title: "Cancel", style: .Cancel) { _ in
            dispatch_async(dispatch_get_main_queue(), {
                let dimmed = navigationBar.subviews.flatMap { $0.alpha < 1 ? $0 : nil }
                UIView.animateWithDuration(0.25) {
                    dimmed.forEach { $0.alpha = 1 }
                }
            })
            return
        })

        alertController.addAction(UIAlertAction(title: "Go back", style: .Default) { _ in
            dispatch_async(dispatch_get_main_queue(), {
                self.popViewControllerAnimated(true)
            })
        })

        presentViewController(alertController, animated: true, completion: nil)

        return false
    }
}

Тепер у будь-якому контролері просто дотримуйтесь RequestsNavigationPopVerificationі така поведінка прийнята за замовчуванням.


1

Використовуйте isMovingFromParentViewController

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

    if self.isMovingFromParentViewController {
        // current viewController is removed from parent
        // do some work
    }
}

Чи можете ви пояснити далі, як це підтверджує натискання кнопки "назад"?
Мюррей Сагал

Це просто, але це працює лише в тому випадку, якщо ви впевнені, що повернетесь до цього виду з будь-якого дочірнього виду, який ви можете завантажити. Якщо дитина пропустить цей погляд, повертаючись до батьківського, ваш код не буде викликаний (представлення вже зникло, не перемістившись з батьків). Але це те саме питання, коли обробляти події тільки при запуску кнопки "Назад", як це запитує ОП. Тож це проста відповідь на його запитання.
CMont

Це супер просто і елегантно. Я це люблю. Лише одна проблема: це також спрацює, якщо користувач провела пальцями, щоб повернутися назад, навіть якщо він скасує посередині. Можливо, кращим рішенням було б ввести цей код viewDidDisappear. Таким чином, він запуститься лише після того, як вигляд точно не піде.
Phontaine Judd

1

Відповідь від @William правильна, однак, якщо користувач запускає рух пальцем по назад, viewWillDisappearметод викликається і навіть selfне буде в навігаційному стеку (тобто self.navigationController.viewControllersне міститиме self), навіть якщо проведіть пальцем не завершено, і контролер перегляду насправді не вискочив. Таким чином, рішенням було б:

  1. Вимкніть рух пальцем назад viewDidAppearі поверніть, а дозволити лише використання кнопки "назад", використовуючи:

    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
    }
  2. Або просто використовувати viewDidDisappearзамість цього:

    - (void)viewDidDisappear:(BOOL)animated
    {
        [super viewDidDisappear:animated];
        if (![self.navigationController.viewControllers containsObject:self])
        {
            // back button was pressed or the the swipe-to-go-back gesture was
            // completed. We know this is true because self is no longer
            // in the navigation stack.
        }
    }

0

Я знайшов рішення, яке поки що не дуже приємне, але воно працює для мене. Приймаючи цю відповідь , я також перевіряю, програмно чи ні, я:

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];

  if ((self.isMovingFromParentViewController || self.isBeingDismissed)
      && !self.isPoppingProgrammatically) {
    // Do your stuff here
  }
}

Ви повинні додати це властивість до свого контролера та встановити його ТАК перед тим, як програмно вискакувати:

self.isPoppingProgrammatically = YES;
[self.navigationController popViewControllerAnimated:YES];

0

Знайшли новий спосіб зробити це:

Ціль-С

- (void)didMoveToParentViewController:(UIViewController *)parent{
    if (parent == NULL) {
        NSLog(@"Back Pressed");
    }
}

Швидкий

override func didMoveToParentViewController(parent: UIViewController?) {
    if parent == nil {
        println("Back Pressed")
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.