UINavigationController и UINavigationBarDelegate.ShouldPopItem() с MonoTouch

Как мне открыть UIAlertView, когда была нажата кнопка "Назад" UINavigationBar (контролируемая UINavigationController)? При определенных условиях я хочу спросить пользователя: "Вы уверены?" тип вопроса, чтобы он мог либо прервать действие, либо остаться в текущем представлении, либо поместить стек навигации и перейти к родительскому виду.

Самый привлекательный подход, который я нашел, - переопределить ShouldPopItem() в UINavigationBar Delegate.

Теперь здесь есть довольно похожий вопрос: iphone navigationController: дождитесь ответа uialertview перед выходом из текущего представления

Есть также несколько других вопросов подобного характера, например здесь: Проверьте, собирается ли UIViewController выходить из стека навигации? и Как сказать, когда кнопка возврата нажата в UINavigationControllerStack

Все эти состояния "подкласс UINavigationController" как можно более отвечают.

Тогда есть тот, который читает как подклассификация UINavigationController, как правило, не очень хорошая идея: Monotouch: UINavigationController, переопределить initWithRootViewController

apple docs также говорят, что UINavigationController не предназначен для подкласса.

Несколько других утверждают, что переопределение функции ShouldPopItem() невозможно даже при использовании UINavigationController, поскольку это не позволяет назначить UINavigationBarDelegate пользовательского/подкласса UINavigationBar.

Ни одна из моих попыток подкласса не работала, мой пользовательский делегат не был принят.

Я также где-то читал, что может быть возможно реализовать ShouldPopItem() в моем пользовательском UINavigationController, поскольку он присваивает себя как делегат своего UINavigationBar.

Не удивительно, что это не сработало. Как бы подкласс UINavigationController знал о методах, принадлежащих UINavigationBarDelegate. Он был отклонен: "подходящий метод не найден, чтобы переопределить". Удаление скомпилированного ключевого слова "override", но метод полностью игнорируется (как и ожидалось). Я думаю, что с Obj-C можно было бы реализовать несколько протоколов (аналогично интерфейсам в С# AFAIK), чтобы добиться этого. К сожалению, UINavigationBarDelegate не является интерфейсом, а классом в MonoTouch, поэтому это невозможно.

Я здесь очень потерялся. Как переопределить ShouldPopItem() в UINavigationBar Delegate, когда он контролируется UINavigationController? Или есть какой-либо другой способ всплывать UIAlertView и ждать его результата, прежде чем, возможно, вытащить стек навигации?

Ответ 1

Для справки, маршрут, который я взял после отказа от ShouldPopItem(), - это заменить кнопку "назад" на UIBarButtonItem, у которой есть пользовательский UIButton, назначенный как CustomView. UIButton создается как оригинальная кнопка назад, используя два изображения для нормального и нажатого состояния. Наконец, требуется скрытие исходной кнопки возврата.

Слишком много кода для того, что он должен делать. Так что да, спасибо Apple.

BTW: Еще одна возможность заключается в создании UIButton с секретным UIButtonType 101 (который на самом деле является кнопкой "назад" ), но я избегал этого, так как он может сломаться в любой более поздней версии iOS.

Ответ 2

Это сообщение немного устарело, но в случае, если вас все еще интересует решение (по-прежнему связано с подклассом):

Это реализует "Вы уверены, что хотите выйти?" предупреждение при нажатии кнопки "Назад", измененное с помощью кода здесь: http://www.hanspinckaers.com/custom-action-on-back-button-uinavigationcontroller/

Оказывается, если вы реализуете UINavigationBarDelegate в CustomNavigationController, вы можете использовать метод shouldPopItem:


CustomNavigationController.h:

#import <Foundation/Foundation.h>

@interface CustomNavigationController : UINavigationController <UIAlertViewDelegate, UINavigationBarDelegate> {

BOOL alertViewClicked;
BOOL regularPop;
}

@end

CustomNavigationController.m:

#import "CustomNavigationController.h"
#import "SettingsTableController.h"

@implementation CustomNavigationController


- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {

if (regularPop) {
    regularPop = FALSE;
    return YES;
}

if (alertViewClicked) {
    alertViewClicked = FALSE;
    return YES;
}

if ([self.topViewController isMemberOfClass:[SettingsTableViewController class]]) {
    UIAlertView * exitAlert = [[[UIAlertView alloc] initWithTitle:@"Are you sure you want to quit?" message:nil delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Yes", nil] autorelease];

    [exitAlert show];

    return NO;

}   
else {
    regularPop = TRUE;
    [self popViewControllerAnimated:YES];
    return NO;
}   
}

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 0) {
    //Cancel button
}

else if (buttonIndex == 1) {    
        //Yes button
    alertViewClicked = TRUE;
    [self popViewControllerAnimated:YES];
}           
}

@end

Странная логика с "regularPop" bool заключается в том, что по какой-то причине просто возврат "YES" на shouldPopItem только всплывает навигация, а не вид, связанный с navBar - для этого вам нужно напрямую вызвать popViewControllerAnimated (который затем вызывает shouldPopItem как часть своей логики.)

Ответ 3

Переопределить только методы UINavigationBarDelegate в подклассе UINavigationController, и он должен просто работать. Будьте осторожны в том, что методы протокола также вызывают, когда вы нажимаете или выталкиваете контроллер вида изнутри своего кода, а не только при нажатии кнопки "Назад". Это потому, что они являются push/pop notifications, а не нажатиями нажатых кнопок.

Ответ 4

Xamarin предоставляет интерфейс IUINavigationBarDelegate, позволяющий реализовать UINavigationBarDelegate как часть вашего пользовательского класса UINavigationController.

Однако интерфейс не требует реализации метода ShouldPopItem. Весь интерфейс делает добавление соответствующего атрибута Protocol к классу, поэтому его можно использовать как UINavigationBarDelegate.

Итак, вам нужно добавить объявление ShouldPopItem в класс следующим образом:

[Export ("navigationBar:shouldPopItem:")]
public bool ShouldPopItem (UINavigationBar navigationBar, UINavigationItem item)
{
}

Ответ 5

Я объединил это решение с нативным решением Obj-C. Именно так я сейчас обрабатываю отмену кнопки BACK в iOS

Кажется, что можно обрабатывать метод shouldPopItem для NavigationBar следующим образом:

  • Подкласс a UINavigationController
  • Отметьте свой пользовательский UINavigationController с помощью IUINavigationBarDelegate
  • Добавьте этот метод с атрибутом Export

    [Экспорт ( "navigationBar: shouldPopItem:" )] public bool ShouldPopItem (UINavigationBar navigationBar, элемент UINavigationItem) { }

Теперь вы можете обрабатывать метод ShoulPopItem. Примером этого является создание такого интерфейса, как

public interface INavigationBackButton
{
    // This method should return TRUE to cancel the "back operation" or "FALSE" to allow normal back
    bool BackButtonPressed();
}

Затем отметьте свой UIViewController, который должен обрабатывать кнопку "Назад" с помощью этого интерфейса. Реализуйте что-то вроде этого

public bool BackButtonPressed()
{
    bool needToCancel = // Put your logic here. Remember to return true to CANCEL the back operation (like in Android)
    return needToCancel;
}

Тогда в вашей реализации ShouldPopItem есть что-то вроде этого танки для: https://github.com/onegray/UIViewController-BackButtonHandler/blob/master/UIViewController%2BBackButtonHandler.m

        [Export("navigationBar:shouldPopItem:")]
        public bool ShouldPopItem(UINavigationBar navigationBar, UINavigationItem item)
        {
            if (this.ViewControllers.Length < this.NavigationBar.Items.Length)
                return true;

            bool shouldPop = true;
            UIViewController controller = this.TopViewController;
            if (controller is INavigationBackButton)
                shouldPop = !((INavigationBackButton)controller).BackButtonPressed();

            if (shouldPop)
            {
                //MonoTouch.CoreFoundation.DispatchQueue.DispatchAsync
                CoreFoundation.DispatchQueue.MainQueue.DispatchAsync(
                    () =>
                    {
                        PopViewController(true);
                    });
            }
            else
            {
                // Workaround for iOS7.1. Thanks to @boliva - http://stackoverflow.com/posts/comments/34452906
                foreach (UIView subview in this.NavigationBar.Subviews)
                {
                    if(subview.Alpha < 1f)
                        UIView.Animate(.25f, () => subview.Alpha = 1);
                }

            }

            return false;                          
        }