UINavigationController and UINavigationBarDelegate.ShouldPopItem() with MonoTouch

懵懂的女人 提交于 2019-12-28 06:01:49

问题


How do I pop up an UIAlertView when the back button of a UINavigationBar (controlled by a UINavigationController) was tapped? Under certain conditions, I want to ask the user an "Are you sure?" type of question so he could either abort the action and stay on the current view or pop the navigation stack and go to the parent view.

The most appealing approach I found was to override ShouldPopItem() on UINavigationBar's Delegate.

Now, there is a quite similar question here: iphone navigationController : wait for uialertview response before to quit the current view

There are also a few other questions of similar nature, for example here: Checking if a UIViewController is about to get Popped from a navigation stack? and How to tell when back button is pressed in a UINavigationControllerStack

All of these state "subclass UINavigationController" as possible answers.

Then there is this one that reads like subclassing UINavigationController is generally not a good idea: Monotouch: UINavigationController, override initWithRootViewController

The apple docs also say that UINavigationController is not intended to be subclassed.

A few others state that overriding ShouldPopItem() is not even possible when using a UINavigationController as that does not allow to assign a custom/subclassed UINavigationBarDelegate to the UINavigationBar.

None of my attempts of subclassing worked, my custom Delegate was not accepted.

I also read somewhere that it might be possible to implement ShouldPopItem() within my custom UINavigationController since it assigns itself as Delegate of its UINavigationBar.

Not much of a surprise, this didn't work. How would a subclass of UINavigationController know of the Methods belonging to UINavigationBarDelegate. It was rejected: "no suitable method found to override". Removing the "override" keyword compiled, but the method is ignored completely (as expected). I think, with Obj-C one could implement several Protocols (similar to Interfaces in C# AFAIK) to achieve that. Unfortunately, UINavigationBarDelegate is not an Interface but a Class in MonoTouch, so that seems impossible.

I'm pretty much lost here. How to override ShouldPopItem() on UINavigationBar's Delegate when it is controlled by a UINavigationController? Or is there any other way to pop up an UIAlertView and wait for it's result before possibly popping the navigation stack?


回答1:


This post is a bit old, but in case you're still interested in a solution (still involves subclassing though):

This implements a "Are you sure you want to Quit?" alert when the back button is pressed, modified from the code here: http://www.hanspinckaers.com/custom-action-on-back-button-uinavigationcontroller/

Turns out if you implement the UINavigationBarDelegate in the CustomNavigationController, you can make use of the shouldPopItem method:


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

The weird logic with the "regularPop" bool is because for some reason just returning "YES" on shouldPopItem only pops the navbar, not the view associated with the navBar - for that to happen you have to directly call popViewControllerAnimated (which then calls shouldPopItem as part of its logic.)




回答2:


For reference, the route I took after giving up on ShouldPopItem() is to replace the back button with a UIBarButtonItem that has a custom UIButton assigned as it's CustomView. The UIButton is crafted to look like the original back button using two images for the normal and the pressed state. Finally, hiding the original back button is required.

Way too much code for what it's supposed to do. So yeah, thanks Apple.

BTW: Another possibility is creating a UIButton with the secret UIButtonType 101 (which is actually the back button) but I avoided this as it may break at any later iOS version.




回答3:


Override only UINavigationBarDelegate methods in a UINavigationController subclass and it should simply work. Be cautious that the protocol methods are also called when you push or pop a view controller from inside your code and not only when the back button is pressed. This is because them are push/pop notifications not button pressed actions.




回答4:


Xamarin does provide the IUINavigationBarDelegate interface to allow you to implement the UINavigationBarDelegate as part of your custom UINavigationController class.

The interface however does not require that the ShouldPopItem method be implemented. All the interface does is add the appropriate Protocol attribute to the class so it can be used as a UINavigationBarDelegate.

So in addition you need to add the ShouldPopItem declaration to the class as follows:

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



回答5:


I've merged this solution with a native Obj-C solution. This is the way I'm currently handling the cancellation of the BACK button in iOS

It seems that it is possible to handle the shouldPopItem method of the NavigationBar in this way:

  1. Subclass a UINavigationController
  2. Mark your custom UINavigationController with the IUINavigationBarDelegate
  3. Add this method with the Export attribute

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

Now you can handle popping in the ShoulPopItem method. An example to this is to create an interface like this

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

Then mark your UIViewController which needs to handle the back button with this interface. Implement something like this

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

Then in your ShouldPopItem Implementation have something like this tanks to: 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;                          
        }


来源:https://stackoverflow.com/questions/6413595/uinavigationcontroller-and-uinavigationbardelegate-shouldpopitem-with-monotouc

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!