RubyMotion - les onglets de navigation

Publié le 12 décembre 2013 par Nicolas Cavigneaux | mobile

Cet article est publié sous licence CC BY-NC-SA

Dans cet article à propos de RubyMotion, nous allons voir comment mettre en place des onglets de navigation pour améliorer le confort utilisateur de notre application de Todo.

Jusqu’ici nous étions par défaut sur le contrôleur permettant l’ajout d’une tâche. Une fois la tâche ajoutée, nous étions automatiquement redirigé vers le contrôleur de liste des tâches. Depuis cette liste, une icône dans la barre d’en-tête nous permettait de revenir vers le formulaire.

Nous allons améliorer notre application en proposant à l’utilisateur de naviguer comme bon lui semble grâce à un UITabBar bien plus convivial.

Vous pouvez récupérer le code de notre application sur GitHub et vous placer sur le commit “dd7a7bcebd” qui correspond à l’état dans lequel nous avons laissé l’application à la fin de l’article précédent.

Création du contrôleur de navigation

La première chose à faire pour cette évolution est d’indiquer à notre application que nous voulons utiliser une navigation à base d’onglets plutôt que le système d’application à vue unique.

C’est donc le fichier app/app_delegate.rb qui va être concerné. Plutôt que d’instancier le TaskViewController et de le déclarer comme rootViewController, nous allons créer une méthode qui instancie un UITabBarController et définit ses différents onglets. C’est ensuite cette méthode qui sera utilisée pour définir le rootViewController :

class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)
    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
    @window.rootViewController = appTabBarController
    @window.makeKeyAndVisible

    true
  end

  def appTabBarController
    tabBarController = UITabBarController.alloc.init
    tabBarController.viewControllers = [
      TaskViewController.alloc.init,
      UINavigationController.alloc.initWithRootViewController(ListViewController.alloc.initWithStyle(UITableViewStylePlain))
    ]
    tabBarController
  end
end

Le menu à onglet est en place et fonctionnel mais sans titre, ni icône :

UITabBar sans titre

Modifions le code pour nommer nos onglets :

class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)
    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
    @window.rootViewController = appTabBarController
    @window.makeKeyAndVisible

    true
  end

  def appTabBarController
    taskViewController = TaskViewController.alloc.init
    taskViewController.title = "Ajouter"

    listViewController = ListViewController.alloc.initWithStyle(UITableViewStylePlain)
    listViewController.title = "Tâches"

    tabBarController = UITabBarController.alloc.init
    tabBarController.viewControllers = [
      taskViewController,
      UINavigationController.alloc.initWithRootViewController(listViewController)
    ]

    tabBarController
  end
end

Comme vous pouvez le voir, pour résoudre notre problème nous n’avons eu qu’à donner un titre à nos contrôleurs. Ce titre est utilisé automatiquement par les UITabBarItems.

Voici le résultat :

UITabBar avec titres

Nous pourrions également définir des icônes pour chaque contrôleur via la propriété .tabBarItem.image mais nous ne le ferons pas ici.

Quelques problèmes entachent notre application suite à ces modifications :

  • la barre de navigation couvre partiellement notre bouton d’ajout de tâche
  • l’affichage de l’onglet “Tâches” fait apparaître une en-tête non-désirée générée par la UITabBarController
  • le bouton de validation de tâche fait planter l’application car elle utilise un système de transition incompatible avec le nouveau fonctionnement
  • depuis la liste des tâches, le bouton de retour à la liste ne fonctionne que partiellement (il affiche le bon contenu mais sans changer d’onglet)

Problème d'en-tête

Corrigeons ces quelques problèmes.

Hauteur de barre de navigation

Pour corriger le problème de la barre de navigation qui couvre partiellement le bouton d’ajout de tâche, deux solutions s’offrent à nous. Nous pourrions simplement remonter le bouton ou alors réduire la hauteur de la barre de navigation.

Optons pour cette deuxième solution qui nous permettra de découvrir de nouvelles choses.

Pour agir sur la position et la taille de la UITabBar, il faut redéfinir sa propriété frame. Nous allons donc la définir dans notre méthode appTabBarController du fichier app/app_delegate.rb juste après l’avoir instanciée :

def appTabBarController
  

  tabBarController = UITabBarController.alloc.init
  tabBarController.tabBar.frame = [[0, UIScreen.mainScreen.bounds.size.height - 20], [UIScreen.mainScreen.bounds.size.width, 20]]

  
end

On positionne donc la UITabBar sur le bord gauche de l’écran et à 20 px de la bordure du bas. On définit la taille avec une largeur égale à celle de l’écran et une hauteur de 20 px.

Voici ce que nous obtenons :

UITabBar avec taille personnalisée

En-tête non-désirée

Deux problèmes existent sur l’onglet “Tâches”. Premièrement, la barre de statut d’iOS s’affiche. Il y a également l’en-tête auto-générée par le UITabBarController.

Masquer la barre de statut

Nous n’avons pas le souci avec notre contrôleur d’ajout de tâches car nous lui avons déjà demandé explicitement de masquer la barre de statut. Appliquons le même principe au contrôleur de liste des tâches.

Dans le fichier app/controllers/list_view_controller.rb, il faut ajouter la méthode suivante :

def prefersStatusBarHidden
  true
end

Je l’ajoute en ce qui me concerne dans les méthodes privées. Voici le résultat obtenu :

UITabBarController sans status bar

Changer de l’en-tête du UITabBarController

Il faut maintenant remplacer l’en-tête générée par le UITabBarController par notre en-tête personnalisée qui est actuellement affichée comme en-tête de notre tableau de tâches.

Commençons donc par supprimer les deux méthodes qui servent à la génération de cet en-tête dans notre tableau. Les deux méthodes concernées sont tableView:viewForHeaderInSection et tableView:heightForHeaderInSection:.

Notre tableau ne nous affiche donc plus que son contenu, sans en-tête :

UITableView sans en-tête

Nous devons changer l’en-tête par défaut, pour cela nous allons dans le viewDidLoad définir une image de fond pour la navigationBar puis changer son texte pour y mettre le titre de notre application :

def viewDidLoad
  self.navigationController.navigationBar.setBackgroundImage(UIImage.imageNamed("bgHeader.png"), forBarMetrics:UIBarMetricsDefault)

  self.navigationController.navigationBar.titleTextAttributes = {
    NSForegroundColorAttributeName => UIColor.colorWithRed(0.702, green: 0.702, blue: 0.702, alpha: 1.000),
    NSFontAttributeName => UIFont.fontWithName("AvenirNext-Bold", size: 25)
  }

  self.title = "RubyMotion Todo"
  self.navigationController.tabBarItem.title = "Tâches"
end

La première ligne nous permet d’utiliser notre image de fond. Ensuite nous définissons les attributs du texte de titre pour la navigationBar. Attention a bien utiliser les noms de classe comme clé sinon cette ligne n’aura aucun effet. C’est pour cette raison que j’utilise l’ancienne syntaxe de Hash.

Les deux dernières lignes permettent de définir le titre. En changeant le title du contrôleur, la chaîne est mise-à-jour dans l’en-tête mais cette manipulation a pour effet de bord de redéfinir le titre du bouton d’onglet en bas. C’est donc pourquoi la dernière ligne redéfinie à nouveau le titre de ce bouton.

En-tête personnalisé

Il nous manque toujours le bouton de suppression d’une tâche. Nous ne remettrons pas le bouton de retour au formulaire puisque la navigation par onglet nous le permet.

Cet ajout est très simple puisqu’il consiste à ajouter le code suivant à la fin de la méthode viewDidLoad :

leftButtonItem = UIBarButtonItem.alloc.initWithCustomView(deleteButton)
self.navigationItem.setLeftBarButtonItem(leftButtonItem)

Les navigationBars sont prévus pour recevoir un bouton à gauche et à droite. Notre méthode générant le bouton existant déjà nous n’avions plus qu’à la ré-utiliser pour instancier un UIBarButtonItem et le placer à gauche.

Noter toutefois que j’ai légèrement réduit la taille du bouton pour qu’il s’intègre mieux visuellement :

Bouton de suppression

Correction du bouton d’ajout de tâche

Notre bouton d’ajout de tâche pose un souci. Au moment de la transition du contrôleur d’ajout vers le contrôleur de liste des tâches, l’application plante car le contrôleur de liste initialisé ligne 18 de app/controllers/task_view_controller.rb n’est pas appelé dans le contexte attendu. Il n’intègre pas la notion de navigationController qui est requis dans notre nouveau mode de fonctionnement.

Il n’y a maintenant plus aucune raison d’initialiser ce contrôleur à cette endroit. En effet, il est déjà initialisé dans app/app_delegate.rb au chargement de l’application et intégré en tant que contrôleur lié au UITabBarController.

On peut donc retirer le code suivant de la méthode addTask :

@listViewController = ListViewController.alloc.initWithStyle(UITableViewStylePlain)
@listViewController.view.frame = self.view.frame

UIView.transitionFromView(self.view,
                          toView: @listViewController.view,
                          duration: 0.5,
                          options: UIViewAnimationOptionTransitionFlipFromLeft,
                          completion: nil)

Si vous testez à nouveau l’application vous verrez que l’ajout de tâche fonctionne à nouveau. Il reste une chose à gérer, la transition automatique vers l’onglet de la liste des tâches. Nous retournons donc dans la méthode addTask pour ajouter le code suivant :

self.tabBarController.selectedIndex = 1

Cette ligne suffira donc à indiquer l’index de l’onglet souhaité et à l’afficher.

Conclusion

Nous avons pu mettre en place une navigation à base d’onglets, bien plus intuitive et flexible pour les utilisateurs.

Passer d’une application “single-page” à une application basée sur les onglets implique quelques changements dans la gestion, la logique et la présentation mais se fait finalement sans trop d’efforts.

L’utilisation des UITabBars est très courante dans les applications iOS, c’est donc un élément clé à maîtriser.

Cette simple modification donne plus de cachet à l’application et permet également d’étendre nos possibilités en tant que développeur. On évite également d’entasser les éléments de navigation dans le peu d’endroit qu’il nous reste à disposition dans notre interface.

Vous trouverez l’ensemble du code d’exemple cet article, découpé en commits sur GitHub.


L’équipe Synbioz.

Libres d’être ensemble.