I. Création d'une application iOS▲
RubyMotion est livré avec un ensemble d'outils simplifiant la gestion d'un projet, sa compilation, son déploiement sur un iDevice ou encore son débogage. On a donc une commande motion qui permet notamment de créer le squelette d'un projet, de mettre à jour RubyMotion ou encore de créer un ticket de support.
Au sein du projet, un Rakefile est à notre disposition nous permettant de :
- compiler le projet pour le simulateur ou un device ;
- créer une archive .ipa pour distribution ;
- obtenir les variables de configuration du projet ;
- générer les ctags pour faciliter la navigation dans l'éditeur de texte ;
- lancer le simulateur ;
- lancer les batteries de tests ;
- compiler le code sous la forme de bibliothèque réutilisable.
Créons notre premier projet de test :
$
motion create HelloWorld
Create HelloWorld
Create HelloWorld/app/app_delegate.rb
Create HelloWorld/Rakefile
Create HelloWorld/resources/Default-568h@2x.png
Create HelloWorld/spec/main_spec.rb
Nous pouvons donc aller dans le répertoire nouvellement créé et vérifier la configuration par défaut
$
cd HelloWorld
rake config
background_modes : []
build_dir : "./build"
codesign_certificate : "iPhone Developer: Nicolas Cavigneaux (8KBAAAKF4)"
delegate_class : "AppDelegate"
deployment_target : "6.1"
device_family : :iphone
entitlements : {}
files : ["./app/app_delegate.rb"
]
fonts : []
framework_search_paths : []
frameworks : ["UIKit"
, "Foundation"
, "CoreGraphics"
]
icons : []
identifier : "com.yourcompany.HelloWorld"
interface_orientations : [:portrait, :landscape_left, :landscape_right]
libs : []
motiondir : "/Library/RubyMotion"
name : "HelloWorld"
prerendered_icon : false
provisioning_profile : "/Users/nico/Library/MobileDevice/Provisioning Profiles/338B4DF5-111F-498F-BD4A-AAAAAAAAAB.mobileprovision"
resources_dirs : ["./resources"
]
sdk_version : "6.1"
seed_id : "8ZBABAA36Y"
short_version : "1"
specs_dir : "./spec"
status_bar_style : :default
version : "1.0"
weak_frameworks : []
xcode_dir : "/Applications/Xcode.app/Contents/Developer"
On voit donc le certificat de signature (nécessaire au déploiement sur un iDevice) utilisé, le type de cible (ici « iphone » et iOS 6.1 minimum), les frameworks Cocoa chargés, l'icône qui sera utilisée pour présenter l'application, l'identifiant de l'application, les orientations supportées, le nom de l'application, le répertoire de ressources (images, sons…) ou encore le numéro de version.
I-A. Structure d'un projet▲
On peut maintenant ouvrir le projet dans un éditeur pour en apprendre plus. Commençons par le Rakefile :
# -*- coding: utf-8 -*-
$:
.unshift(
"/Library/RubyMotion/lib"
)
require
'motion/project/template/ios'
Motion::
Project::
App.setup do
|
app|
# Use `rake config' to see complete project settings.
app.name =
'HelloWorld'
end
C'est à cet endroit qu'est défini le type de template à utiliser, ici « ios » qui détermine le framework et la cible du projet. On voit également que le nom de l'application est défini dans le bloc setup. On peut donc y définir des valeurs personnalisées pour chaque variable de configuration disponible.
Il y a ensuite le fichier app/app_delegate.rb qui est le point d'entrée de l'application :
class
AppDelegate
def
application(
application, didFinishLaunchingWithOptions:
launchOptions)
true
end
end
Par défaut, l'application ne fera qu'afficher un écran noir. C'est dans la méthode application que nous pouvons instancier notre fenêtre principale soit via du code Ruby, soit en chargeant un fichier InterfaceBuilder (.xib).
Vous pouvez donc compiler et lancer l'application dans le simulateur :
$
rake
Build ./build/iPhoneSimulator-6
.1
-Development
Compile ./app/app_delegate.rb
Create ./build/iPhoneSimulator-6
.1
-Development/HelloWorld.app
Link ./build/iPhoneSimulator-6
.1
-Development/HelloWorld.app/HelloWorld
Create ./build/iPhoneSimulator-6
.1
-Development/HelloWorld.app/Info.plist
Create ./build/iPhoneSimulator-6
.1
-Development/HelloWorld.app/PkgInfo
Copy ./resources/Default-568h@2x.png
Create ./build/iPhoneSimulator-6
.1
-Development/HelloWorld.dSYM
Simulate ./build/iPhoneSimulator-6
.1
-Development/HelloWorld.app
(
main)>
Un binaire est généré dans le répertoire build puis le simulateur lance l'application. On obtient notre fameux écran noir dans le simulateur.
Le répertoire resources quant à lui ne contient qu'une image noire par défaut. Le répertoire spec contient lui un fichier de test d'exemple :
describe "Application 'HelloWorld'"
do
before do
@app
=
UIApplication.sharedApplication
end
it "has one window"
do
@app
.windows.size.should ==
1
end
end
Les tests utilisent la bibliothèque Bacon qui est un clone allégé de RSpec. Ils permettent de tester facilement, comme d'habitude en Ruby, les différents aspects de l'application que ce soit les vues, les contrôleurs ou encore les modèles.
Vous pouvez lancer les tests via rake spec, l'unique test par défaut ne passera pas. Il vérifie que l'application a bien une fenêtre (UIWindow) ce qui n'est pas le cas de notre application actuelle. Nous verrons plus en détail l'écriture des tests dans un prochain article.
Notez que vous pouvez créer de nouveaux répertoires dans app/ pour organiser votre code. Il sera donc courant dans les projets d'avoir les répertoires :
- app/views : vues personnalisées ( ex. : un TableView amélioré par vos soins) ;
- app/models : classes définissant des objets métier ;
- app/controllers : comme en Rails, ce sont les aiguilleurs qui permettent aux vues de communiquer avec les modèles ou les services externes.
II. REPLÂ : Console interactive▲
Vous l'aurez peut-être remarqué en lançant la tâche rake, une fois l'application lancée dans le simulateur, le terminal nous rend la main dans une console interactive IRB qui nous permet d'inspecter et de manipuler l'application en cours d'exécution :
(main)> p self
main
=> main
(main)> alert = UIAlertView.new
=> #<UIAlertView:0x96735f0>
(main)> alert.message = "Hello World!"
=> "Hello World!"
(main)> alert.show
=> #<UIAlertView:0x96735f0>
Nous venons, depuis la console interactive, de créer et d'instancier une modale contenant le texte « Hello World » puis nous l'avons affichée. Elle est donc visible dans le simulateur !
Un outil dont vous ne pourrez plus vous séparer tellement il est pratique. Il n'existe aucun équivalent en Objective C ou dans XCode, cet outil est une merveille.
Si vous faites ⌘-clic sur la modale, vous verrez que self change pour devenir #<UITextEffectsWindow:0x9437340>. On peut de cette manière inspecter n'importe quel élément très facilement.
III. Création d'une fenêtre▲
Nous allons maintenant ajouter à notre application une fenêtre principale pour y afficher notre « Hello World ». Pour cela nous allons retourner dans le fichier app/app_delegate.rb pour instancier la fenêtre et l'associer à un contrôleur :
class
AppDelegate
def
application(
application, didFinishLaunchingWithOptions:
launchOptions)
@window
=
UIWindow.alloc.initWithFrame(
UIScreen.mainScreen.bounds)
@window
.rootViewController =
MainViewController.alloc.init
@window
.makeKeyAndVisible
true
end
end
Tout d'abord nous créons une UIWindow en initialisant sa taille à celle de l'écran.
Une fois cette fenêtre créée, nous définissons le contrôleur qui lui est associé et qui servira à régir son comportement. Nous allons ici utiliser le contrôleur MainViewController que nous allons écrire par la suite.
Une fois la fenêtre créée, nous la marquons comme étant la fenêtre principale qui sera donc chargée automatiquement au lancement de l'application.
Pour finir, nous retournons true ce qui est nécessaire au lancement de l'application.
Voyons maintenant le contrôleur !
class
MainViewController <
UIViewController
def
viewDidLoad
view.backgroundColor =
UIColor.scrollViewTexturedBackgroundColor
end
end
Nous redéfinissons ici la méthode viewDidLoad qui est appelée automatiquement dès que la vue associée au contrôleur est chargée. Ce que nous faisons dans cette méthode est tout simple, en effet on ne fait que changer le fond de la fenêtre. Par défaut celle-ci est noire, elle utilisera une texture grise disponible par défaut sous iOS.
Nous aurions pu utiliser une couleur pleine :
class
MainViewController <
UIViewController
def
viewDidLoad
view.backgroundColor =
UIColor.greenColor
end
end
Nous aurions dans ce cas un fond vert :
III-A. Utiliser une image de fond▲
Il est possible d'utiliser une image en background. Il faut tout d'abord mettre à disposition cette image dans le répertoire resources. Dans notre exemple, ce fichier s'appelle customBackground.jpg :
class
MainViewController <
UIViewController
def
viewDidLoad
background_view =
UIImageView.alloc.initWithFrame(
UIScreen.mainScreen.bounds)
background_view.image =
UIImage.imageNamed(
"customBackground.jpg"
)
self
.view.addSubview(
background_view)
end
end
Nous commençons donc par instancier une vue pour l'image via une UIImageView que l'on cale sur la taille de l'écran. On instancie ensuite l'image en elle-même (UIImage) en utilisant son nom de fichier. Il ne nous reste finalement plus qu'à ajouter cette nouvelle vue à la vue principale.
Nous aurions pu utiliser loadView plutôt que viewDidLoad pour que l'image soit chargée avant même que la vue n'apparaisse, nous verrons cela dans l'exemple suivant.
IV. Ajout d'un label▲
Nous allons maintenant ajouter deux labels qui permettront d'afficher « Hello World » ainsi que l'heure au chargement de la vue. Commençons par le plus simple à savoir le label « Hello World ».
IV-A. Label « Hello World »▲
class
MainViewController <
UIViewController
def
loadView
self
.view =
UIView.alloc.init
background_view =
UIImageView.alloc.initWithFrame(
UIScreen.mainScreen.bounds)
background_view.image =
UIImage.imageNamed(
"customBackground.jpg"
)
self
.view.addSubview(
background_view)
helloLabel =
UILabel.alloc.initWithFrame [[
110
, 30
]
, [
200
, 50
]]
helloLabel.backgroundColor =
UIColor.clearColor
helloLabel.textColor =
UIColor.whiteColor
helloLabel.text =
"Hello World!"
self
.view.addSubview(
helloLabel)
end
end
Nous utilisons loadView pour préparer le style et les éléments de la vue, nous devons donc instancier nous-mêmes la vue associée au contrôleur. loadView se charge normalement de ça, mais puisque nous écrasons la méthode par défaut, il faut penser à le faire manuellement.
On définit le background comme précédemment. Vient ensuite la création du label, on commence donc par instancier le label (UILabel) en définissant sa taille. On utilise d'ailleurs ici un sucre syntaxique de RubyMotion. Notre [[110, 30], [200, 50]] correspond en fait à un CGMakeRect(110, 30, 200, 50). Le premier nombre correspond à la position en X, le deuxième en Y, le troisième à la largeur du label et le dernier à sa hauteur.
On s'assure que le fond du label soit transparent et que le texte soit en blanc. Finalement on définit le texte du label puis on l'ajoute à la vue principale.
IV-B. Label heure courante▲
class
MainViewController <
UIViewController
def
loadView
self
.view =
UIView.alloc.init
background_view =
UIImageView.alloc.initWithFrame(
UIScreen.mainScreen.bounds)
background_view.image =
UIImage.imageNamed(
"customBackground.jpg"
)
self
.view.addSubview(
background_view)
helloLabel =
UILabel.alloc.initWithFrame [[
110
, 20
]
, [
200
, 50
]]
helloLabel.backgroundColor =
UIColor.clearColor
helloLabel.textColor =
UIColor.whiteColor
helloLabel.text =
"Hello World!"
self
.view.addSubview(
helloLabel)
timeLabel =
UILabel.alloc.initWithFrame [[
90
, 60
]
, [
200
, 50
]]
timeLabel.backgroundColor =
UIColor.clearColor
timeLabel.textColor =
UIColor.whiteColor
# Initialisation d'un calendrier à la date / heure actuelles
calendar =
NSCalendar.alloc.initWithCalendarIdentifier(
NSGregorianCalendar)
date =
NSDate.date
calendar.components(
NSMinuteCalendarUnit, fromDate:
date)
# Utilisation de la locale fr_FR pour formater les dates
fr_FR =
NSLocale.alloc.initWithLocaleIdentifier "fr_FR"
format =
NSDateFormatter.alloc.init
format.locale =
fr_FR
format.setDateFormat(
"dd MMM yyyy - HH:mm"
)
# Conversion de la date en chaine
dateString =
format.stringFromDate(
date)
timeLabel.text =
dateString
self
.view.addSubview(
timeLabel)
end
end
La première partie est identique à l'exemple précédent.
Nous initialisons ensuite un calendrier (au format grégorien) puis nous récupérons la date courante. On définit la précision souhaitée pour la décomposition de la date puis on force la locale française pour obtenir des dates en français.
Une fois cela fait on peut définir le format de date souhaité en sortie. Il ne nous reste plus qu'à générer la chaîne puis à l'utiliser dans le label. Finalement le label est ajouté à la vue principale.
V. Conclusion▲
En quelques lignes de code, nous avons une application fonctionnelle. Certes elle n'est pas très utile, mais démontre la facilité d'écriture d'une application iOS (UI incluse) en RubyMotion. Nous aurions pu construire l'UI dans l'interface builder et l'importer dans le code ce qui nous aurait encore épargné des lignes de code mais nous verrons cela dans un article à venir.
Dans le prochain article sur RubyMotion, nous verrons comment gérer un formulaire basique, récupérer et traiter les entrées utilisateur.
Vous trouverez le code de cet article sur GitHub
Remerciements▲
Cet article est publié avec l'aimable autorisation de Synbioz, l'article original peut être lu sur le blog de Synbioz : Introduction à RubyMotion.
Nous tenons à remercier ClaudeLELOUP pour sa relecture attentive de cet article.