Распознать нажатие на заголовок панели навигации

Может ли кто-нибудь помочь мне распознать касание, когда пользователь нажимает Заголовок на панели навигации?

Я хотел бы распознать это нажатие, а затем анимировать появление tableHeaderView. Возможно, сдвинув TableView вниз.

Идея заключается в том, что пользователь может затем выбрать быстрый вариант (из tableViewHeader) для повторного заполнения TableView.

Однако я не могу распознать никаких кранов.

Я использую Свифт.

Спасибо.


person Richard    schedule 09.10.2014    source источник


Ответы (9)


UINavigationBar не раскрывает свою внутреннюю иерархию представлений. Нет поддерживаемого способа получить ссылку на UILabel, отображающий заголовок.

Вы можете копаться в его иерархии представлений «вручную» (путем поиска по его subviews), но это может перестать работать в будущем выпуске iOS, поскольку иерархия представлений является частной.

Одним из обходных путей является создание UILabel и установка его в качестве navigationItem.titleView вашего контроллера представления. Вам решать, чтобы соответствовать стилю ярлыка по умолчанию, который может меняться в разных версиях iOS.

Тем не менее, это довольно легко настроить:

override func didMove(toParentViewController parent: UIViewController?) {
    super.didMove(toParentViewController: parent)

    if parent != nil && self.navigationItem.titleView == nil {
        initNavigationItemTitleView()
    }
}

private func initNavigationItemTitleView() {
    let titleView = UILabel()
    titleView.text = "Hello World"
    titleView.font = UIFont(name: "HelveticaNeue-Medium", size: 17)
    let width = titleView.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)).width
    titleView.frame = CGRect(origin:CGPoint.zero, size:CGSize(width: width, height: 500))
    self.navigationItem.titleView = titleView

    let recognizer = UITapGestureRecognizer(target: self, action: #selector(YourViewController.titleWasTapped))
    titleView.userInteractionEnabled = true
    titleView.addGestureRecognizer(recognizer)
}

@objc private func titleWasTapped() {
    NSLog("Hello, titleWasTapped!")
}

Я устанавливаю размер метки на ее естественную ширину (используя sizeThatFits:), но я устанавливаю ее высоту на 500. Панель навигации сохранит ширину, но уменьшит высоту до собственной высоты панели. Это максимизирует область, доступную для касания (поскольку естественная высота метки может составлять всего около 22 пунктов, а высота полосы составляет 44 пункта).

person rob mayoff    schedule 09.10.2014
comment
Идеальное решение. Я полностью согласен с отказом от поиска в иерархии представлений, как и в других найденных мной примерах, так как это может не сработать в будущем. Спасибо большое. - person Richard; 10.10.2014
comment
Я был почти один, но не подозревал userInteractionEnabled. +1. - person Ahmed Khalaf; 28.09.2016
comment
У меня проблема с этим решением. При более поздних обновлениях текста в uilabel сама метка по какой-то причине переходит на x 0 (крайний левый угол экрана). Похоже на ошибку в UIKit. Во всяком случае вокруг? (iOS 10.1.1) - person Jonny; 04.11.2016
comment
Я не смог воспроизвести указанную выше проблему на 100%, однако, похоже, на данный момент она исчезла, если я сделаю это ПОСЛЕ того, как я изменил текст указанного UILabel: [self.navigationItem.titleView.superview setNeedsLayout];. Это действительно остановило переход метки на X 0. - person Jonny; 04.11.2016
comment
Это было бы моим предложением. - person rob mayoff; 04.11.2016
comment
Я только что попробовал свое приложение на iOS11, и высота 500 не работает должным образом: она не изменяется, поэтому метка может не отображаться или может отображаться в середине экрана. Я изменил его на высоту панели навигации: 44. - person Ferran Maylinch; 20.09.2017

Из ответов мы можем сказать, что есть два подхода к этому.

  1. Добавьте UITapGestureRecognizer к titleView. Это не кажется элегантным и требует, чтобы вы вручную установили шрифт заголовка панели навигации, поэтому я бы не рекомендовал это делать.
  2. Добавьте UITapGestureRecognizer к navigationBar. Это кажется довольно элегантным, но проблема с опубликованными ответами, использующими этот подход, заключается в том, что все они приводят к тому, что элементы управления на панели навигации не работают. Вот моя реализация этого метода, которая позволяет вашим элементам управления продолжать работать.

// Declare gesture recognizer
var tapGestureRecognizer: UITapGestureRecognizer!

override func viewDidLoad() {

    // Instantiate gesture recognizer
    tapGestureRecognizer = UITapGestureRecognizer(target:self, action: #selector(self.navigationBarTapped(_:)))
}

override func viewWillAppear(_ animated: Bool) {

    // Add gesture recognizer to the navigation bar when the view is about to appear
    self.navigationController?.navigationBar.addGestureRecognizer(tapGestureRecognizer)

    // This allows controlls in the navigation bar to continue receiving touches
    tapGestureRecognizer.cancelsTouchesInView = false
}

override func viewWillDisappear(_ animated: Bool) {

    // Remove gesture recognizer from navigation bar when view is about to disappear
    self.navigationController?.navigationBar.removeGestureRecognizer(tapGestureRecognizer)
}

// Action called when navigation bar is tapped anywhere
@objc func navigationBarTapped(_ sender: UITapGestureRecognizer){

    // Make sure that a button is not tapped.
    let location = sender.location(in: self.navigationController?.navigationBar)
    let hitView = self.navigationController?.navigationBar.hitTest(location, with: nil)

    guard !(hitView is UIControl) else { return }

    // Here, we know that the user wanted to tap the navigation bar and not a control inside it 
    print("Navigation bar tapped")

}
person Zia    schedule 05.04.2018
comment
Я не знал о sender.location(in Это умный способ. Я не могу проголосовать достаточно. - person bibscy; 11.09.2018
comment
Хорошее решение. Мне нравится viewWillDisappear, который удаляет его для дочерних представлений. Хороший! - person smileBot; 20.07.2021

Это решение, пусть и не супер элегантное. В раскадровке просто поместите обычный UIButton над заголовком и прикрепите его к IBAction в вашем ViewController. Возможно, вам придется сделать это для каждого представления.

person Steve Rosenberg    schedule 09.10.2014
comment
Это отличное решение, на самом деле, работает идеально и легко реализуется. - person Evgenii; 30.06.2015

Простым подходом может быть просто создание распознавателя жестов касания и присоединение его к элементу панели навигации.

// on viewDidLoad
let tapGestureRecognizer = UITapGestureRecognizer(target:self, action: #selector(YourViewController.somethingWasTapped(_:)))
self.navigationController?.navigationBar.addGestureRecognizer(tapGestureRecognizer)

func somethingWasTapped(_ sth: AnyObject){
    print("Hey there")
}
person Bruno Cunha    schedule 02.02.2017
comment
Лучший способ сделать это. - person Federico Malagoni; 10.02.2017
comment
В моем случае это нарушило функциональность кнопки «Назад» после того, как в стек был помещен другой контроллер представления. - person Georg; 10.03.2017
comment
Это нарушит функциональность других кнопок на панели навигации. - person Ghulam Rasool; 18.05.2017
comment
Пожалуйста, смотрите мой ответ. Он использует этот подход, но сохраняет функциональность кнопок на панели навигации. - person Zia; 03.05.2018
comment
Миллион долларов ответ (Y) - person Umair_UAS; 10.12.2019

Ответ Бруно был для меня на 90%. Однако я заметил одну вещь: функциональность UIBarButtonItem для контроллера навигации перестала работать в других контроллерах представления после того, как к нему был добавлен этот распознаватель жестов. Чтобы исправить это, я просто удаляю жест из контроллера навигации, когда представление готовится исчезнуть:

var tapGestureRecognizer : UITapGestureRecognizer!

override func viewWillAppear(_ animated: Bool) {

  tapGestureRecognizer = UITapGestureRecognizer(target:self, action: #selector(self.navBarTapped(_:)))

  self.navigationController?.navigationBar.addGestureRecognizer(tapGestureRecognizer)

}

override func viewWillDisappear(_ animated: Bool) {

  self.navigationController?.navigationBar.removeGestureRecognizer(tapGestureRecognizer)

}

func navBarTapped(_ theObject: AnyObject){

  print("Hey there")

}
person megaBreezy    schedule 03.04.2017
comment
это гораздо более простое решение, если речь идет в первую очередь о нажатии на панель навигации, а не только на заголовок в частности! Он также повторно использует существующий UIView и не требует введения нового представления. - person Karsten; 08.07.2018

Что касается navigationItem.titleView, это действительно UIView, в итоге я использовал UIButton, который дает всю гибкость из коробки.

override func viewDidLoad() {

    // Create title button
    let titleViewButton = UIButton(type: .system)
    titleViewButton.setTitleColor(UIColor.black, for: .normal)
    titleViewButton.setTitle("Tap Me", for: .normal)

    // Create action listener
    titleViewButton.addTarget(self, action: #selector(YourViewController.titleViewButtonDidTap), for: .touchUpInside)

    // Set the title view with newly created button
    navigationItem.titleView = titleViewButton
}

@objc func titleViewButtonDidTap(_ sender: Any) {
    print("Title did tap")
}
person Mahmut C    schedule 16.06.2018

Есть более простое и элегантное решение с использованием распознавателей жестов (по крайней мере, для iOS 9 и выше).

UITapGestureRecognizer * titleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(titleTapped)];
[self.navigationItem.titleView addGestureRecognizer:titleTapRecognizer];

Затем добавьте метод tapped title:

-(void) titleTapped {
    // Called when title is tapped
}
person James Andrews    schedule 12.09.2016
comment
Не забудьте определить self.navigationItem.titleView как в принятом ответе. По умолчанию ноль. - person Jonny; 04.11.2016

Зия: Ваше решение guard !(hitView is UIControl) хорошо работало для меня при работе под iOS 9, но когда я запускаю тот же код в более новых версиях iOS, он не видит hitView как UIControl, когда отключенная кнопка barButton отключена.

У меня есть несколько элементов UIBarButtonItems на панели навигации. Когда эти barButtons включены (hitView is UIControl) в моем действии UITapGestureRecognizer работает правильно, и функция действия завершается. Но если UIBarButtonItem отключен и пользователь нажимает на кнопку, (hitView is UIControl) имеет значение false, и код выполняется так, как если бы пользователь нажал на панель навигации.

Единственный способ обойти эту проблему, который я нашел, - это получить объект UIView для всех моих кнопок barButtons в viewWillAppear с помощью:

button1View = button1Item.value(forKey: "view") as? UIView

так далее...

А затем в моей функции действия UITapGestureRecognizer я тестирую:

if [button1View, button2View, button3View, button4View].contains(hitView)
{
  return
}

Это уродливый обходной путь! Любая идея, почему (hitView - это UIControl) должен возвращать false на отключенной кнопке панели?

person WholeCheese    schedule 18.12.2018
comment
Обновление: кажется, что мой обходной путь работает только в iOS 9. В iOS 12 hitView не распознается как мой barButton. У меня нет устройства для разработки iOS 12, поэтому я не могу это отлаживать. Но я предполагаю, что в iOS 12 отключенный barButton не рассматривается как UIControl или возвращаемый hitView является базовой панелью навигации. - person WholeCheese; 21.12.2018

Просто поместите Transparent UIButton в пользовательский заголовок.

    let maskButton = UIButton(frame: .zero)
    // ....
    // your views...
    // add Transparent button at last
    addSubview(maskButton)
    maskButton.snp.makeConstraints({
        $0.edges.equalToSuperview()
    })
    // listener tap event 
    maskButton.addTarget(self,action:#selector(buttonClicked)....
person GYOTO    schedule 13.05.2021