iOS 7:如何为UIControlStateHighlighted设置UIBarButtonItem backButtonBackgroundImage?

时间:2014-05-14 21:55:53

标签: ios ios7 uibarbuttonitem navigationbar

我正在尝试在正常和突出显示状态下设置后退按钮的背景图像。

- (void)configureBackButtonInNavigationItem:(UINavigationItem *)item
{
    UIBarButtonItem *backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"back"
            style:UIBarButtonItemStyleBordered target:nil action:NULL];
    [backBarButtonItem setTitleTextAttributes:@{NSForegroundColorAttributeName : [UIColor whiteColor]} forState:UIControlStateNormal];
    [backBarButtonItem setTitleTextAttributes:@{NSForegroundColorAttributeName : [UIColor orangeColor]} forState:UIControlStateHighlighted];

    // white arrow image
    UIImage *normalImage = [[[UIImage imageNamed:@"btn_normal"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] resizableImageWithCapInsets:UIEdgeInsetsMake(0.f, 17.f, 0.f, 0.f)];

    // orange arrow image
    UIImage *pressedImage = [[[UIImage imageNamed:@"btn_on_press"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] resizableImageWithCapInsets:UIEdgeInsetsMake(0.f, 17.f, 0.f, 0.f)];

    [backBarButtonItem setBackButtonBackgroundImage:normalImage
                    forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
    [backBarButtonItem setBackButtonBackgroundImage:pressedImage
                forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];

    [backBarButtonItem setBackgroundImage:normalImage
                    forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
    [backBarButtonItem setBackgroundImage:pressedImage
                forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];

    NSLog(@"NORMAL: %@ HIGHLIGHTED: %@", [backBarButtonItem backButtonBackgroundImageForState:UIControlStateNormal barMetrics:UIBarMetricsDefault],
                [backBarButtonItem backButtonBackgroundImageForState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault]);
    item.backBarButtonItem = backBarButtonItem;

    NSLog(@"NORMAL: %@ HIGHLIGHTED: %@", [backBarButtonItem backButtonBackgroundImageForState:UIControlStateNormal barMetrics:UIBarMetricsDefault],
                [backBarButtonItem backButtonBackgroundImageForState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault]);
}

输出如下:

NORMAL: <_UIResizableImage: 0x16b55e10> HIGHLIGHTED: <_UIResizableImage: 0x16b593d0>
NORMAL: <_UIResizableImage: 0x16b55e10> HIGHLIGHTED: <_UIResizableImage: 0x16b593d0>

但突出显示状态的观察结果只是调整了设置为正常状态而不是使用正确的高亮显示图像。

正常:

Normal state of back bar button item

突出显示(箭头仍然是白色,按钮意外变暗):

Highlighted state of back bar button item

请不要将有关leftBarButtonItem或UIButton用法的答案发布为自定义视图。这两种方法都可以在iOS 7上实现刷卡到后退行为。

UPD:关于这个问题的填充雷达#17481106。

UPD2:雷达#17481106已在iOS 8中修复。

3 个答案:

答案 0 :(得分:5)

目前Apple在interactivePopGestureRecognizer上有错误(这会在刷回推动画后冻结导航控制器的视图,你会在控制台中看到nested pop animation can result in corrupted navigation bar警告),顺便说一下,我们可以做一个小的破解那个bug。

这是一个适合我的解决方案,

对NavigationController类进行子类化并使其委派手势

@interface CBNavigationController : UINavigationController 
@end

@implementation CBNavigationController

- (void)viewDidLoad
{
  __weak CBNavigationController *weakSelf = self;

  if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
  {
    self.interactivePopGestureRecognizer.delegate = weakSelf;
    self.delegate = weakSelf;
  }
}

// Hijack the push method to disable the gesture

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
  if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
    self.interactivePopGestureRecognizer.enabled = NO;

  [super pushViewController:viewController animated:animated];
}

#pragma mark UINavigationControllerDelegate

- (void)navigationController:(UINavigationController *)navigationController
       didShowViewController:(UIViewController *)viewController
                    animated:(BOOL)animate
{
  // Enable the gesture again once the new controller is shown

  if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
    self.interactivePopGestureRecognizer.enabled = YES;
}


@end

当用户在转换过程中开始向后滑动时,弹出事件会叠加并“损坏”导航堆栈。我的解决方法是在推送转换期间暂时禁用手势识别器,并在新视图控制器加载时再次启用它。同样,使用UINavigationController子类更容易。

在此之后,您可以冷静地使用item.leftBarButtonItemUIButton作为自定义视图。

答案 1 :(得分:3)

除了l0gg3r的回答之外,您还可以创建UINavigationBar的子类,您可以在其中实现l0gg3r的逻辑并自定义您的后退按钮。
之后,您只需从故事板中将类名设置为navigationBar。

这样的事情:

#import "MyNavigationBar.h"

#import <objc/runtime.h>
#import <objc/message.h>    

#pragma mark - UINavigationController category

@interface UINavigationController (InteractiveGesture) <UINavigationControllerDelegate, UIGestureRecognizerDelegate>

- (void)fixInteractivePopGesture;

@end

@implementation UINavigationController (InteractiveGesture)

- (void)fixInteractivePopGesture
{
    __weak UINavigationController *weakSelf = self;

    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.interactivePopGestureRecognizer.delegate = weakSelf;
        self.delegate = weakSelf;
    }

    [self swizzleOriginalSelectorWithName:@"pushViewController:animated:" 
                       toSelectorWithName:@"myPushViewController:animated:"];
}

#pragma mark - Swizzle method
- (void)swizzleOriginalSelectorWithName:(NSString *)origName toSelectorWithName:(NSString *)swizzleName
{
    Method origMethod = class_getInstanceMethod([self class], NSSelectorFromString(origName));
    Method newMethod = class_getInstanceMethod([self class], NSSelectorFromString(swizzleName));
    method_exchangeImplementations(origMethod, newMethod);
}

- (void)myPushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.interactivePopGestureRecognizer.enabled = NO;
    }

    [self myPushViewController:viewController animated:animated];
}

#pragma mark UINavigationControllerDelegate

- (void)navigationController:(UINavigationController *)navigationController
       didShowViewController:(UIViewController *)viewController
                    animated:(BOOL)animate
{
    // Enable the gesture again once the new controller is shown        
    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.interactivePopGestureRecognizer.enabled = YES;
    }
}

@end

#pragma mark - MyNavigationBar

@interface MyNavigationBar()

@property (strong, nonatomic) UIButton *backButtonCustomView;

@end

@implementation MyNavigationBar

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self setup];
    }
    return self;
}

- (void)awakeFromNib
{
    [super awakeFromNib];

    [self setup];
}

- (void)setup
{
    self.backButtonCustomView = [UIButton buttonWithType:UIButtonTypeCustom];
    // here customize your button        
    // e.g. set images for Normal state, or highlighted state, etc...
    // ...
    [self.backButtonCustomView addTarget:self action:@selector(handleBackButton:) forControlEvents:UIControlEventTouchUpInside];

    self.backButton = [[UIBarButtonItem alloc] initWithCustomView: self.backButtonCustomView];        
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    if ([[self navigationController] viewControllers].count > 1) {
        [self.topItem setLeftBarButtonItem:self.backButton animated:YES];
    }

    // Enabling back "Swipe from edge to pop" feature.
    [self.navigationController fixInteractivePopGesture];
}

- (void)handleBackButton:(id)sender
{
    UINavigationController *nvc = [self navigationController];

    [nvc popViewControllerAnimated:YES];
}

- (UINavigationController *)navigationController
{
    UINavigationController *resultNC = nil;
    UIViewController *vc = nil;
    for (UIView* next = [self superview]; next; next = next.superview) {
        UIResponder* nextResponder = [next nextResponder];

        if ([nextResponder isKindOfClass:[UIViewController class]]) {
            vc = (UIViewController*)nextResponder;
            break;
        }
    }

    if (vc) {
        if ([vc isKindOfClass:[UINavigationController class]]) {
            resultNC = (UINavigationController *)vc;
        } else {
            resultNC = vc.navigationController;
        }
    }

    return resultNVC;
}

@end

然后:
enter image description here

你走了。而已!现在您可以将该类复制/粘贴到您想要的任何项目中,只需从storyboard中设置类名:)

答案 2 :(得分:-3)

您不想要自定义视图,因为它会破坏滑动,但您应该添加此行。

self.navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self; 

您的代码如下所示。

UIImage *normalImage = [[[UIImage imageNamed:@"btn_normal"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] resizableImageWithCapInsets:UIEdgeInsetsMake(0.f, 17.f, 0.f, 0.f)];
UIImage *pressedImage = [[[UIImage imageNamed:@"btn_on_press"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] resizableImageWithCapInsets:UIEdgeInsetsMake(0.f, 17.f, 0.f, 0.f)];    

UIButton *customBackButton = [UIButton buttonWithType:UIButtonTypeCustom];
[customBackButton setBackgroundImage:normalImage forState:UIControlStateNormal];
[customBackButton setBackgroundImage:pressedImage forState:UIControlStateHighlighted];
[customBackButton addTarget:self action:@selector(customBackMethod:) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *customBackBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:customBackButton];
self.navigationItem.leftBarButtonItem = customBackBarButtonItem;
self.navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;

- (IBAction)customBackMethod:(id)sender {
    [self.navigationController popViewControllerAnimated:YES];
}