Подтвердить что ты не робот

IOS 7: Как установить UIBarButtonItem backButtonBackgroundImage для UIControlStateHighlighted?

Я пытаюсь установить фоновое изображение для кнопки "Назад" в нормальных и подсвеченных состояниях.

- (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:

Normal state of back bar button item

Выделен (стрелка по-прежнему белая, кнопка затемняется неожиданно):

Highlighted state of back bar button item

Пожалуйста, не публикуйте ответы на вопрос об использовании leftBarButtonItem или UIButton в качестве пользовательского представления. Оба этих подхода управляют движением пальца в обратном направлении, доступным на iOS 7.

UPD: заполненный радар # 17481106 по этому вопросу.

UPD2: радар # 17481106 исправлен в iOS 8.

4b9b3361

Ответ 1

В настоящее время у Apple есть ошибка на interactivePopGestureRecognizer (что позволяет заморозить навигационное представление контроллера после того, как вы вернетесь на анимацию push, вы увидите предупреждение nested pop animation can result in corrupted navigation bar в консоли), кстати, мы можем сделать небольшой взлом для работы эта ошибка.

Вот решение, которое отлично подходит для меня,

Подкласс класса 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

Когда пользователь начинает прокручивать назад в середине перехода, поп-события складываются и "повреждают" стек навигации. Мое обходное решение состоит в том, чтобы временно отключить распознаватель жестов во время push-переходов и снова включить его при загрузке нового контроллера. Опять же, это проще с подклассом UINavigationController.

После этого вы можете спокойно использовать item.leftBarButtonItem и UIButton как пользовательский вид.

Ответ 2

В дополнение к ответу l0gg3r вы можете создать подкласс UINavigationBar, где вы можете реализовать логику l0gg3r и настроить свою кнопку возврата.
После этого вам просто нужно установить имя класса в свой навигационный блок из раскадровки.

Что-то вроде этого:

#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

Здесь вы идете. Это оно! Теперь вы можете просто скопировать/вставить этот класс в любой проект, который вы хотите, и просто установить имя класса из раскадровки:)

Ответ 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];
}