Зміна кнопки повернення назад в iOS 7 вимикає проведення пальцем для переходу назад


81

У мене є додаток для iOS 7, де я встановлюю власну кнопку повернення, як це:

    UIImage *backButtonImage = [UIImage imageNamed:@"back-button"];
    UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];

    [backButton setImage:backButtonImage forState:UIControlStateNormal];
    backButton.frame = CGRectMake(0, 0, 20, 20);

    [backButton addTarget:self
                   action:@selector(popViewController)
         forControlEvents:UIControlEventTouchUpInside];

    UIBarButtonItem *backBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
    viewController.navigationItem.leftBarButtonItem = backBarButtonItem;

Але це вимикає жест iOS 7 "проведення пальцем зліва направо" для переходу до попереднього контролера. Хто-небудь знає, як я можу встановити власну кнопку і при цьому тримати цей жест увімкненим?

EDIT: Натомість я намагався встановити viewController.navigationItem.backBarButtonItem, але, схоже, це не відображає моє власне зображення.


Я ще не знайшов належного рішення для цього ?? Хтось знайшов хороше рішення та пояснив, чому це працює ??.
user1010819

Як щодо використання добре зробленої сторонньої бібліотеки: SwipeBack ?
devxoul

Відповіді:


82

ВАЖЛИВО: Це хак. Я б порекомендував поглянути на цю відповідь .

Зателефонувавши за наступним рядком після призначення leftBarButtonItemпрацюючого для мене:

self.navigationController.interactivePopGestureRecognizer.delegate = self;

Редагувати: це не працює, якщо викликати initметоди. Це слід викликати viewDidLoadабо подібними методами.


Ви встановлюєте це на контролері перегляду? або контролер навігації? У мене така сама проблема, але, здається, це не працює?
Kevin Renskers

1
@mixedCase Після завантаження зразкового проекту я тепер розумію вашу проблему. Код працює до тих пір, поки вміст подання колекції не перевищує горизонтальної ширини контролера перегляду. Однак, як тільки подання колекції стає горизонтально прокручуваним, воно замінює interactivePopGestureRecognizer. Я подивлюсь, чи зможу я обійтися.
Paul Hunter,

8
У мене проблема з цим кодом. Це працює недовго, після 5-10 зворотних пальців ВК замерзає. Будь-яке рішення?
Тимур Бернікович

1
Тимур Бернікович. Я переживаю те саме. Ви знайшли для цього причини?
user1010819

4
Встановлення делегата для selfвидалення його з об’єкта класу _UINavigationInteractiveTransition. Відповідальність цього об'єкта полягає в тому, щоб навігаційному контролеру не повідомляли про те, що він вже переходить. Все ще розслідує, чи можливо ввімкнути цей жест чи ні, коли кнопка "Назад" є власною.
Saltymule

56

Використовуйте властивості backIndicatorImage і backIndicatorTransitionMaskImage панелі UINavigationBar, якщо це можливо. Встановивши їх на UIAppearanceProxy, можна легко змінити поведінку у вашому додатку. Зморшка полягає в тому, що ви можете встановити лише на ios 7, але це вдається, оскільки ви в будь-якому випадку можете використовувати лише поп-жест на ios 7. Ваша звичайна стилізація ios 6 може залишитися цілою.

UINavigationBar* appearanceNavigationBar = [UINavigationBar appearance];
//the appearanceProxy returns NO, so ask the class directly
if ([[UINavigationBar class] instancesRespondToSelector:@selector(setBackIndicatorImage:)])
{
    appearanceNavigationBar.backIndicatorImage = [UIImage imageNamed:@"back"];
    appearanceNavigationBar.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"back"];
    //sets back button color
    appearanceNavigationBar.tintColor = [UIColor whiteColor];
}else{
    //do ios 6 customization
}

Спроба маніпулювати делегатом interactivePopGestureRecognizer призведе до багатьох проблем.


4
Дякую Ден, це дійсно важливо, і це має бути прийнятою відповіддю. Просто перепризначивши делегата, ви відкриваєте себе БАГАТО дивної поведінки. Зокрема, коли користувачі намагаються провести пальцем назад по topViewController.
Кріс Вагнер

1
Це рішення передбачає, що все, що ви хочете зробити, це змінити зображення кнопки "Назад". Але що, якщо ви хочете змінити текст кнопки "Назад" та / або дію, виконану при натисканні кнопки "Назад"?
user102008

У цей момент вам було б краще обслуговувати, встановивши UINavigationItem.leftBarButtonItem. Існує безліч відповідей, які можна знайти за допомогою пошуку leftBarButtonItem у google або stackoverflow.
Saltymule

Якщо ви хочете обмежити зміну зовнішнього вигляду вашим власним інтерфейсом і не впливати на навігаційні панелі, створені, наприклад, ABPeoplePickerNavigationControllerви можете використовувати власний UINavigationControllerпідклас:[[UINavigationBar appearanceWhenContainedIn:[THNavigationController class], nil] setBackIndicatorImage:[UIImage imageNamed:@"btn_back_arrow"]]; [[UINavigationBar appearanceWhenContainedIn:[THNavigationController class], nil] setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"btn_back_arrow_highlighted"]];
tom

2
На жаль, це не працює на iOS8. Кнопка "Назад" не змінює свій вигляд на моє власне зображення.
Lensflare,

29

Я бачив це рішення http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/, які підкласи UINavigationController. Це найкраще рішення, оскільки воно обробляє випадок, коли ви проводите пальцем до того, як контролер встановлений, що спричиняє збій.

На додаток до цього я помітив, що якщо ви проводите пальцем по кореневому контролеру подання (після натискання на один і назад), інтерфейс стає неактивним (також та сама проблема у відповіді вище).

Отже, код у підкласі UINavigationController повинен виглядати так:

@implementation NavigationController

- (void)viewDidLoad {
    [super viewDidLoad];
    __weak NavigationController *weakSelf = self;

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

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
    // Hijack the push method to disable the gesture
    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
    self.interactivePopGestureRecognizer.enabled = ([self respondsToSelector:@selector(interactivePopGestureRecognizer)] && [self.viewControllers count] > 1);
}

@end

1
мали ті самі проблеми з проведенням на кореневому контролері подання, дякую!
Michael Rose

КРАЩЕ РІШЕННЯ НА ЦІЛІЙ СТОРІНЦІ. ПРАЦЮЄ ДОБРО Добре. ДЯКУЮ
Шахід Ікбал

19

я використовую

[[UINavigationBar appearance] setBackIndicatorImage:[UIImage imageNamed:@"nav_back.png"]];
[[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"nav_back.png"]];

[UIBarButtonItem.appearance setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -64) forBarMetrics:UIBarMetricsDefault];

2
В інших місцях я бачив коментарі про те, що це викликає проблеми в 64-бітному режимі через помилку в коді Apple на даний момент - просто думав, що я опублікую попередження
Пітер Джонсон,

@PeterJohnson що за помилка?
art-divin

Просто найкраще рішення!
Іготіт

Найпростіше рішення.
tounaobun

6

Я також приховую кнопку "Назад", замінюючи її на спеціальний leftBarItem.
Видалення делегата interactivePopGestureRecognizer після дії натискання спрацювало для мене:

[self.navigationController pushViewController:vcToPush animated:YES];

// Enabling iOS 7 screen-edge-pan-gesture for pop action
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
    self.navigationController.interactivePopGestureRecognizer.delegate = nil;
}

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

@MrNickBarker Дякую, що повідомили! Не могли б ви описати точний сценарій? Я не зміг відтворити його, коли
провев

4
Я встановлював це за методом viewDidLoad. Моїм остаточним рішенням було встановити інший клас як делегат, який просто повертає true для 'gestureRecognizerShouldBegin', якщо в стеку навігації є більше одного контролера перегляду.
Nikola Lajic

MrNickBarker будь-які причини, чому він замерзає, я переживаю те саме
user1010819

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

Це з http://stuartkhall.com/posts/ios-7-development-tips-tricks-hacks , але це спричиняє кілька помилок:

  1. Підсуньте інший viewController до навігаційного контролера, коли проводите пальцем від лівого краю екрана;
  2. Або проведіть пальцем від лівого краю екрана, коли topViewController з’являється з навігаційного контролера;

наприклад, коли відображається rootViewController навігаційного контролера, проведіть пальцем від лівого краю екрана та торкніться чогось (ШВИДКО), щоб просунути іншийViewController у NaviController, а потім

  • RootViewController не реагує на жодну подію дотику;
  • ІншийViewController не буде показаний;
  • Знову проведіть пальцем від краю екрана, буде показано інший контролерViewController;
  • Торкніться користувацької кнопки "Назад", щоб відкрити інший VisitorController, аварія!

Отже, ви повинні реалізувати UIGestureRecognizerDelegateметод self.navigationController.interactivePopGestureRecognizer.delegateприблизно так:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    if (gestureRecognizer == navigationController.interactivePopGestureRecognizer) {
        return !navigationController.<#TODO: isPushAnimating#> && [navigationController.viewControllers count] > 1;
    }
    return YES;
}

1
SwipeBack - це рішення цих проблем.
devxoul

6

Ось швидка версія 3 відповіді Ніка H247

class NavigationController: UINavigationController {
  override func viewDidLoad() {
    super.viewDidLoad()
    if responds(to: #selector(getter: interactivePopGestureRecognizer)) {
      interactivePopGestureRecognizer?.delegate = self
      delegate = self
    }
  }

  override func pushViewController(_ viewController: UIViewController, animated: Bool) {
    if responds(to: #selector(getter: interactivePopGestureRecognizer)) {
      interactivePopGestureRecognizer?.isEnabled = false
    }
    super.pushViewController(viewController, animated: animated)
  }
}

extension NavigationController: UINavigationControllerDelegate {
  func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
    interactivePopGestureRecognizer?.isEnabled = (responds(to: #selector(getter: interactivePopGestureRecognizer)) && viewControllers.count > 1)
  }
}

extension NavigationController: UIGestureRecognizerDelegate {}

3

Спробуйте self.navigationController.interactivePopGestureRecognizer.enabled = YES;


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

avishic, не могли б ви, будь ласка, пояснити, чому налаштування делегата буде працювати? .. Я застряг у тій же проблемі.
user1010819

1

Я не писав цього, але такий блог дуже допоміг і вирішив мої проблеми за допомогою спеціальної кнопки навігації:

http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/

Таким чином, він реалізує власний UINavigationController, який використовує делегат поп-жесту. Дуже чисто і портативно!

Код:

@interface CBNavigationController : UINavigationController <UINavigationControllerDelegate, UIGestureRecognizerDelegate>
@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;
}

Редагувати. Додано виправлення проблем, коли користувач намагається провести пальцем ліворуч на контролері кореневого перегляду:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {

    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)] &&
        self.topViewController == [self.viewControllers firstObject] &&
        gestureRecognizer == self.interactivePopGestureRecognizer) {

        return NO;
    }

    return YES;
}

Інтерфейс користувача зависає, якщо провести пальцем ліворуч на кореневому контролері подання контролера навігації.
JohnVanDijk

@JohnVanDijk Я відредагував відповідь із виправленням, яке, на мою думку, застосував для вирішення цієї проблеми. Минув якийсь час, але це має сенс. В основному, якщо контролер вигляду зверху є контролером кореневого перегляду, тоді ми не будемо реагувати на 'interactivePopGestureRecognizer'
kgaidis

Він приховує мою кнопку назад
Мохаммед Хуссейн

1

RootView

override func viewDidAppear(_ animated: Bool) {
    self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}

ChildView

override func viewDidLoad() {
    self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
    self.navigationController?.interactivePopGestureRecognizer?.delegate = self
} 

extension ChildViewController: UIGestureRecognizerDelegate {}

1

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

- (void)navigationController:(UINavigationController *)navigationController
       didShowViewController:(UIViewController *)viewController
                    animated:(BOOL)animate
{
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        if (self.navigationController.viewControllers.count > 1)
        {
            self.navigationController.interactivePopGestureRecognizer.enabled = YES;
        }
        else
        {
            self.navigationController.interactivePopGestureRecognizer.enabled = NO;
        }
    }
}

0

У мене була подібна проблема, коли я призначав поточний контролер подання в якості делегата для інтерактивного поп-жесту, але порушував жест на будь-яких поданих поданнях або поданнях під видом у стеку навігації. Я вирішив це, встановивши делегата -viewDidAppear, а потім встановив нуль -viewWillDisappear. Це дозволило іншим моїм поглядам працювати коректно.


0

Уявіть, що ми використовуємо шаблон за замовчуванням шаблон головного / детального проекту Apple, де master є контролером подання таблиці, і натискання на нього покаже контролер перегляду деталей.

Ми хочемо налаштувати кнопку "Назад", яка з'являється в контролері детального перегляду. Ось як налаштувати зображення , колір зображення , текст , колір тексту та шрифт кнопки "Назад".


Щоб глобально змінити зображення, колір зображення, колір тексту або шрифт, розмістіть наступне в місці, яке викликається до створення будь-якого з ваших контролерів перегляду (наприклад application:didFinishLaunchingWithOptions:, це гарне місце).

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    UINavigationBar* navigationBarAppearance = [UINavigationBar appearance];

    // change the back button, using default tint color
    navigationBarAppearance.backIndicatorImage = [UIImage imageNamed:@"back"];
    navigationBarAppearance.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"back"];

    // change the back button, using the color inside the original image
    navigationBarAppearance.backIndicatorImage = [[UIImage imageNamed:@"back"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
    navigationBarAppearance.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"back"];

    // change the tint color of everything in a navigation bar
    navigationBarAppearance.tintColor = [UIColor greenColor];

    // change the font in all toolbar buttons
    NSDictionary *barButtonTitleTextAttributes =
    @{
      NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue-Light" size:12.0],
      NSForegroundColorAttributeName: [UIColor purpleColor]
      };

    [[UIBarButtonItem appearance] setTitleTextAttributes:barButtonTitleTextAttributes forState:UIControlStateNormal];

    return YES;
}

Зауважте, ви можете використовувати, appearanceWhenContainedIn:щоб мати більший контроль над тим, на які контролери подання впливають ці зміни, але майте на увазі, що ви не можете пройти[DetailViewController class] , оскільки він міститься всередині UINavigationController, а не у вашому DetailViewController. Це означає, що вам потрібно буде підклас UINavigationController, якщо ви хочете отримати більше контролю над тим, на що це впливає.

Щоб налаштувати текст або шрифт / колір певного елемента кнопки "Назад", ви повинні зробити це в MasterViewController (а не в DetailViewController!). Це здається неінтуїтивним, оскільки кнопка з’являється на DetailViewController. Однак, як тільки ви зрозумієте, що спосіб налаштувати його - це встановити властивість у навігаційному елементі, це починає мати більше сенсу.

- (void)viewDidLoad { // MASTER view controller
    [super viewDidLoad];

    UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithTitle:@"Testing"
                                                                   style:UIBarButtonItemStylePlain
                                                                  target:nil
                                                                  action:nil];
    NSDictionary *barButtonTitleTextAttributes =
    @{
      NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue-Light" size:12.0],
      NSForegroundColorAttributeName: [UIColor purpleColor]
      };
    [buttonItem setTitleTextAttributes:barButtonTitleTextAttributes forState:UIControlStateNormal];
    self.navigationItem.backBarButtonItem = buttonItem;
}

Примітка: спроба встановити titleTextAttributes після встановлення self.navigationItem.backBarButtonItem, здається, не працює, тому їх потрібно встановити, перш ніж присвоювати значення цій властивості.


0

Створіть клас 'TTNavigationViewController', який є підкласом 'UINavigationController', і зробіть свій існуючий контролер навігації цього класу в розкадруванні / класі, Приклад коду в класі -

    class TTNavigationViewController: UINavigationController, UIGestureRecognizerDelegate {

override func viewDidLoad() {
    super.viewDidLoad()
    self.setNavigationBarHidden(true, animated: false)

    // enable slide-back
    if self.responds(to: #selector(getter: UINavigationController.interactivePopGestureRecognizer)) {
        self.interactivePopGestureRecognizer?.isEnabled = true
        self.interactivePopGestureRecognizer?.delegate  = self
    }
}

func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.