web-dev-qa-db-fra.com

Reconnaître, appuyez sur le titre de la barre de navigation

Est-ce que quelqu'un pourrait m'aider à reconnaître un appui lorsqu'un utilisateur appuie sur Titre dans la barre de navigation ?

Je voudrais reconnaître ce tap puis animer le tableHeaderView qui apparaît. Peut-être glisser la TableView vers le bas.

L'idée est que l'utilisateur peut ensuite sélectionner une option rapide (à partir de tableViewHeader) pour re-peupler le TableView.

Cependant, je ne reconnais aucun tapotement.

J'utilise Swift.

Je vous remercie.

31
Richard

UINavigationBar n'expose pas sa hiérarchie de vues interne. Il n'existe aucun moyen pris en charge pour obtenir une référence à la UILabel qui affiche le titre.

Vous pouvez rechercher «manuellement» dans sa hiérarchie de vues (en cherchant dans sa subviews), mais cela pourrait ne plus fonctionner dans une version ultérieure d'iOS car la hiérarchie de vues est privée.

Une solution de contournement consiste à créer une UILabel et à la définir comme navigationItem.titleView de votre contrôleur de vue. C'est à vous de faire correspondre le style de l'étiquette par défaut, qui peut changer d'une version à l'autre d'iOS.

Cela dit, c'est assez facile à mettre en place:

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!")
}

Je règle la taille de l'étiquette sur sa largeur naturelle (en utilisant sizeThatFits:), mais je règle sa hauteur à 500. La barre de navigation conservera la largeur mais réduira la hauteur à sa propre hauteur. Cela maximise la surface disponible pour le tapotement (étant donné que la hauteur naturelle de l’étiquette n’est que de ~ 22 points mais que la barre mesure 44 points).

49
rob mayoff

C'est une solution, mais pas super élégante. Dans le scénario, placez simplement un UIButton normal sur le titre et associez-le à un IBAction dans votre ViewController. Vous devrez peut-être faire cela pour chaque vue.

21
Steve Rosenberg

D'après les réponses, nous pouvons dire qu'il existe deux approches pour le faire.

  1. Ajoutez une tapGestureRecognizer à la titleView. Cela ne semble pas élégant et vous oblige à définir manuellement la police du titre de la barre de navigation afin que je ne le recommande pas. 
  2. Ajoutez une tapGestureRecognizer à navigationBar. Cela semble assez élégant, mais le problème avec les réponses affichées de cette approche est qu’elles empêchent toutes les commandes de la barre de navigation de fonctionner. Voici ma mise en œuvre de cette méthode qui permet à vos contrôles de continuer à fonctionner. 

Swift 3

// Declare gesture recognizer
var tapGestureRecognizer: UITapGestureRecognizer!

override func viewWillAppear(_ animated: Bool) {

    // Add gesture recognizer to the navigation bar when the view is about to appear
    tapGestureRecognizer = UITapGestureRecognizer(target:self, action: #selector(self.navigationBarTapped(_:)))
    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")

}
15
Zia

La réponse de Bruno était là pour moi à 90%. Une chose que j’ai toutefois constatée est que la fonctionnalité UIBarButtonItem pour le contrôleur de navigation a cessé de fonctionner dans les autres contrôleurs de vue, une fois que cette reconnaissance des gestes s’y est ajoutée. Pour résoudre ce problème, je supprime simplement le geste du contrôleur de navigation lorsque la vue est sur le point de disparaître:

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")

}
6
megaBreezy

Il existe une solution plus simple et plus élégante utilisant des reconnaisseurs de geste (du moins pour iOS 9 et supérieur). 

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

Ajoutez ensuite la méthode du titre tapée:

-(void) titleTapped {
    // Called when title is tapped
}
4
Ben Smiley

Une approche simple consiste peut-être simplement à créer le dispositif de reconnaissance des gestes du toucher et à le joindre à votre élément de barre de navigation.

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

func somethingWasTapped(_ sth: AnyObject){
    print("Hey there")
}
3
Bruno Cunha

En ce qui concerne navigationItem.titleView est une UIView en effet, j’ai fini par utiliser une UIButton qui donne toute la souplesse voulue.

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")
}
2
Mahmut C

Zia: Votre solution guard !(hitView is UIControl) fonctionnait bien pour moi sous iOS 9, mais lorsque je lance le même code dans les versions iOS les plus récentes, il ne voit pas le hitView comme un UIControl lorsque le bouton bar activé est désactivé.

J'ai plusieurs UIBarButtonItems dans la barre de navigation. Lorsque ces barres sont activées, l'action (hitView est UIControl) dans mon action UITapGestureRecognizer fonctionne correctement et la fonction action se ferme. Toutefois, si UIBarButtonItem est désactivé et que l'utilisateur appuie sur le bouton, le (hitView is UIControl) est faux et le code continue comme si l'utilisateur avait appuyé sur la barre de navigation.

Le seul moyen que j’ai trouvé pour résoudre ce problème consiste à obtenir l’objet UIView pour tous mes boutons de barre dans viewWillAppear avec:

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

etc...

Et ensuite, dans ma fonction d'action UITapGestureRecognizer, je teste:

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

Ceci est une solution de contournement laide! Avez-vous une idée de la raison pour laquelle (hitView est UIControl) devrait renvoyer faux sur un bouton de barre désactivé?

0
WholeCheese