RubyMotion et Interface builder

Publié le 29 juillet 2015 par Jonathan François | outils

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

Au fil de différents articles, Nicolas vous a permis de découvrir le framework RubyMotion permettant d’écrire des applications iOS et OS X natives en Ruby. Si vous les avez manqués et que le sujet vous intéresse je vous conseille d’en prendre connaissance sur cette page.

Dans cet article, nous allons aborder l’utilisation de l’interface builder d’ Xcode au sein d’une application RubyMotion. L’interface builder permet de créer les différentes vues de notre application par le biais de widgets. Nous retrouvons des objets comme des champs de texte, des tableaux, des menus et bien plus. La modification du design est également possible.

La création des différentes vues via un outil visuel permet un gain de productivité assez impressionnant et permet également d’avoir une vision globale de notre application via l’utilisation des storyboards. Ceci est l’une des caractéristiques les plus intéressantes, introduites dans Xcode 4.2 et iOS 5 SDK. Il rend la vie du développeur iOS plus simple et vous permet de facilement concevoir l’interface utilisateur de votre application iOS.

Dans les articles précédents, vous avez pu constater que pour générer un label dans un contrôleur, le code ressemblait à cela :

class RootViewController < UIViewController
  def viewDidLoad
    @label = UILabel.alloc.initWithFrame [[10, 10], [100, 30]]
    @label.backgroundColor = UIColor.clearColor
    @label.textColor = UIColor.whiteColor
    @label.text = "Votre nom"

    view.addSubview @label
  end

En utilisant l’interface builder (IB), la création se réalise dans l’interface visuelle et nous sélectionnons l’élément via son tag (que l’on détermine dans l’IB) :

class RootViewController < UIViewController
  def viewDidLoad
    @label = self.view.viewWithTag(TAG_DE_NOTRE_ELEMENT)
  end
end

Afin d’illustrer cet article, nous allons créer une application permettant de calculer son indice de masse corporelle (IMC). Cet exemple nous permettra d’aborder les points suivants:

  • création de vues
  • création de formulaire
  • mise en place des relations entre les différentes scènes (vues)
  • gestion de la transmission des données entre les vues (Segue)
  • intégration de notre storyboard au sein de notre application RubyMotion

Une vidéo sera créée à la suite de cette article afin d’en améliorer la compréhension. Les fonctionnalités de l’IB y seront parcourues en détails.

Création du projet via l’interface builder

Pour la création du projet, nous allons utiliser la gem ‘ib’ qui permet de générer le fichier ib.xcodeproj à la racine de notre projet et par la même occasion ouvrir l’IB. Cette gem permet de faire connaître à l’IB nos différentes classes, méthodes et outlets définis dans notre code RubyMotion. La notion d’outlet sera abordée dans le prochain article.

Création du squelette RM de l’application

Comme vu dans les précédents articles, créons notre application RubyMotion via la commande motion create storyboard_article.

Ajoutons la gem 'ib' à notre Gemfile. Après installation, nous avons 3 nouvelles tâches rake à disposition :

rake ib                           # Same as 'ib:open'
rake ib:open                      # Generates ib.xcodeproj and opens it in ...
rake ib:project                   # Generates ib.xcodeproj

Lançons donc rake ib:open pour créer notre projet Xcode et ouvrir l’IB.

Création de notre storyboard

Une fois l’IB lancé, créons notre storyboard via le menu d’Xcode, File -> New -> File… et choisissons Storyboard.

IB create storyboard

Nous enregistrons ce fichier dans le dossier resources de notre application.

Voici à quoi ressemble notre interface builder une fois notre premier View Controller ajouté (en glissant-déposant le widget View Controller dans notre storyboard - Cadre vert de l’image suivante).

storyboard with one controller

Afin de pouvoir identifier les éléments de notre storyboard dans notre code RubyMotion, nous allons spécifier les noms de classe de nos contrôleurs mais également les tags des éléments que nous allons intégrer dans ce contrôleur (cadre bleu).

Via le moteur de recherche de widgets (en bas à droite), nous allons rechercher et ajouter les éléments suivants :

  • Label
  • Text Field
  • Button

Pour ajouter la navigation à notre vue, il suffit de lui rajouter une Navigation Controller via le menu Editor -> Embed In -> Navigation Controller.

storyboard with Navigation controller

Nous devons spécifier le contrôleur d’entrée de notre storyboard et lui renseigner un StoryBoardID afin de pouvoir l’identifier lors du chargement du storyboard dans notre application RubyMotion.

storyboardID

Afin de pouvoir identifier chaque élément de notre contrôleur via notre code RubyMotion, il faut utiliser la notion de tag. Les tags doivent être uniques au sein de notre storyboard.

Sélectionnons chacun des éléments dont nous allons vouloir récupérer ou changer la valeur et spécifions lui son tag.

storyboard specify tag

Créons le deuxième contrôleur dans lequel nous allons afficher le résultat de l’IMC utilisateur. Avec la même démarche que précédemment, nous renseignons le nom de la classe, ImcViewController et lui ajoutons une navigation.

storyboard 2 controllers

Sur le deuxième contrôleur nous avons ajouté les éléments suivants, toujours en leur spécifiant leur tag :

  • Label (pour afficher le score et personnalisé le titre du résultat)
  • Text View (pour afficher le récapitulatif des informations utilisateur)

Maintenant nous souhaitons mettre en place la transition entre ces deux contrôleurs. Concrètement au clic du bouton Calcul nous voulons afficher le controller ImcViewController avec le résultat du calcul IMC.

Dans l’IB, il suffit de sélectionner le bouton Calcul en maintenant la touche Ctrl et en le glissant déposant sur le contrôleur de destination pour créer la relation que l’on appelle Segue.

Plusieurs relations sont possibles, voici un tableau récapitulatif concernant les iOS Segues:

Segues List

Une fois notre relation créée, il est important de l’identifier en spécifiant le nom du Segue afin de pouvoir implémenter le traitement des données dans notre code Ruby.

storyboard create segue

Astuce: l’utilisation des contraintes permet de garder le positionnement de vos éléments selon les différents devices utilisés (ipad, iphone 3G …). Pour cela il suffit de placer vos éléments, de tous les sélectionner et d’utiliser la fonction Add missing constraints. Vous pouvez bien évidemment enregistrer ces contraintes manuellement lorsque vous avez des besoins spécifiques.

storyboard Add constraints

Récapitulons

Voilà ce que nous retrouvons dans notre storyboard :

  • NavigationController pour le contrôleur PersonViewController
  • contrôleur PersonViewController
    • différents labels
    • Segment Control genre - avec le tag 1
    • Text Field name - avec le tag 2
    • Text Field weight - avec le tag 3
    • Text Field size - avec le tag 4
    • Text Field age - avec le tag 5
  • NavigationController pour le contrôleur ImcViewController
  • contrôleur ImcViewController
    • Label title - avec le tag 20
    • Text view description - avec le tag 21
    • Label imc - avec le tag 22
    • Label détails - avec le tag 23
  • Segue CalculYourImcSegue - relation entre PersonViewController et ImcViewController

storyboard vue d’ensemble

Intégration de notre storyboard à notre application RubyMotion

Le fichier app/app_delegate.rb est le point d’entrée de l’application. C’est dans la méthode application que nous pouvons instancier notre fenêtre principale en chargeant notre storyboard.

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

    storyboard = UIStoryboard.storyboardWithName("main", bundle:nil)
    rootVC = storyboard.instantiateViewControllerWithIdentifier("main")

    @window.rootViewController = rootVC
    @window.makeKeyAndVisible

    true
  end
end

Ici nous chargeons le fichier storyboard et déterminons notre root controller via UIStoryboard.storyboardWithName("main", bundle:nil).instantiateViewControllerWithIdentifier("main"). main correspondant au StoryBoardID de notre contrôleur d’entrée, ici il s’agit bien du NavigationController associé à la première vue souhaitée soit le contrôleur PersonViewController.

Il faut ensuite créer les deux contrôleurs PersonViewController et ImcViewController au sein de notre code ruby.

Concernant PersonViewController, dans la méthode viewDidLoad qui est appelé juste après le rendu de notre contrôleur, nous allons identifier les différents éléments par le biais des tags.

Pour cela, nous utilisons :

  self.view.viewWithTag(TAG_DE_NOTRE_ELEMENT)

que nous pouvons mettre dans un helper afin de pouvoir le réutiliser dans toute notre application :

# app/helpers/application_helper.rb
def retrieve_subview_with_tag(topview,tag)
  retval = topview.view.subviews.find { |v| v.tag == tag }
  retval ||= topview.view
end

Voici à quoi ressemble notre contrôleur :

# app/controllers/person_view_controller.rb
class PersonViewController < UIViewController
  GENDER_SELECT = 1
  NAME_TEXT    = 2
  WEIGHT_TEXT  = 3
  SIZE_TEXT    = 4
  AGE_TEXT     = 5


  def viewDidLoad
    @gender = retrieve_subview_with_tag(self, GENDER_SELECT)
    @name   = retrieve_subview_with_tag(self, NAME_TEXT)
    @weight = retrieve_subview_with_tag(self, WEIGHT_TEXT)
    @size   = retrieve_subview_with_tag(self, SIZE_TEXT)
    @age    = retrieve_subview_with_tag(self, AGE_TEXT)
  end

  def prepareForSegue(segue, sender:sender)
    if segue.identifier == "CalculYourImcSegue"
      custom_imc           = calcul_imc(@weight.text, @size.text)
      custom_title         = generate_custom_title(@name.text)
      custom_description   = generate_description_text(@name.text,
                                                    @gender.selectedSegmentIndex,
                                                    @age.text,
                                                    @weight.text,
                                                    @size.text)
      custom_imc_category = imc_category(custom_imc)

      imc_view_controller = segue.destinationViewController
      imc_view_controller.custom_title.text = custom_title
      imc_view_controller.custom_description.text = custom_description
      imc_view_controller.custom_imc.text = custom_imc
      imc_view_controller.custom_imc_category.text = custom_imc_category
    end
  end

end

La méthode prepareForSegue est appelée avant d’initier la transition de contrôleur et nous permet d’identifier quelle transition est appelée via le nom que nous avons renseigné sur notre Segue dans l’IB (if segue.identifier == "CalculYourImcSegue").

L’objet segue passé à la méthode prepareForSegue nous permet également d’identifier le contrôleur de destination. Nous le stockons dans une variable local afin de pouvoir appelé les méthodes disponibles dans ce contrôleur(imc_view_controller = segue.destinationViewController).

Les méthodes calcul_imc, generate_custom_title, generate_description_textet imc_category sont des helpers renvoyant du texte(l’indice IMC, un récapitulatif des informations utilisateurs et la catégorie de votre IMC).

Dans notre contrôleur de destination ImcViewController, nous allons également identifié les éléments de notre vue par leur tag. Puis nous allons créer des méthodes qui seront appelées depuis la méthode prepareForSegue du contrôleur PersonViewController afin de leur passer les données voulues.

# app/controllers/imc_view_controller.rb
class ImcViewController < UIViewController
  TITLE_LABEL      = 20
  DESCRIPTION_TEXT = 21
  IMC_TEXT         = 22
  IMC_CATEGORY     = 23

  def awakeFromNib
    # make sure our views are loaded
    self.view
  end

  def viewDidLoad
    @title_label = retrieve_subview_with_tag(self, TITLE_LABEL)
    @description_text = retrieve_subview_with_tag(self, DESCRIPTION_TEXT)
    @imc = retrieve_subview_with_tag(self, IMC_TEXT)
    @custom_imc_categorie = retrieve_subview_with_tag(self, IMC_CATEGORY)
  end

  def custom_title
    @title_label
  end

  def custom_description
    @description_text
  end

  def custom_imc
    @imc
  end

  def custom_imc_category
    @custom_imc_categorie
  end
end

Ici il est important de mentionné la méthode awakeFromNib car elle permet de s’assurer que les informations de notre storyboard sont bien disponibles au sein de notre contrôleur RubyMotion.

Vous pouvez retrouver le code de l’application sur Github.

Voici les deux écrans de l’application :

screen app 1 screen app 2

Conclusion

L’interface builder et l’utilisation du storyboard permet de concevoir rapidement l’UI et le workflow de notre application. L’intégration à RubyMotion se réalise très bien et permet d’éviter de créer les différents éléments à la main. Une vidéo vous sera prochainement proposée afin de vous donner plus de détails.

Maintenant il n’y a plus qu’à tester et créer sa propre application iOS en 20 minutes…


L’équipe Synbioz.

Libres d’être ensemble.