Core Animation avec MacRuby
Dans mes précédents articles sur MacRuby, nous avons abordé des fondamentaux du framework Cocoa. Je vous propose aujourd'hui une petite récréation qui ne vous apprendra rien, si ce n'est d'essayer de rendre vos applications plus attrayantes. En qualité d'utilisateur de Mac, vous avez l'habitude de voir, dans vos applications, des effets de transition, rotation, ... Prenez par exemple exposé ou Spaces. Ceci est possible grâce à Core Animation dont le but est de vous fournir tous les éléments pour mettre un peu d'animation dans vos développements. Je vous propose de voir cela sur un petit exemple permettant de passer d'une vue à une autre avec un effet de slide :
Les principes
Pour mettre en place cet exemple, nous créerons n vues et n items dans la toolbar. Les vues sont liées entre elles sur le même modèle qu'une liste chainée. Soit, chaque vue possède un lien vers la suivante et un autre vers la précédente.
Quand nous cliquons sur un item de la toolbar, nous faisons défiler les vues par enchainement jusqu'à arriver à celle demandée. Ainsi si par exemple la vue courante est la vue n et que nous souhaitons afficher la vue m :
-
Nous vérifions dans quel sens nous devons aller.
- Si n < m nous devons utiliser un effet de glissement de la droite vers la gauche.
- Si n > m nous devons utiliser un effet de glissement de la gauche vers la droite.
-
Nous faisons le glissement :
- En passant à la vue suivante si n < m
- En passant à la vue précédente si n > m
- Si la nouvelle vue affichée n'est pas celle souhaitée, nous recommençons en retournant au point 2
Mise en place
Pour mettre cela en place, nous avons besoin de déclarer, pour une vue donnée, les liens vers la vue suivante et précédente. Il faut également que chaque vue puisse être identifiée par un numéro. Il n'existe rien dans NSView qui permette cela, nous allons donc créer une classe héritant de NSView :
PreferenceView.rb# PreferenceView.rb
# CARB
#
# Created by greg on 26/03/10.
# Copyright 2010 Grégoire Lejeune. All rights reserved.
class PreferenceView < NSView
# Vue précédente
attr_accessor :nextPreferenceView
# Vue suivante
attr_accessor :previousPreferenceView
# Numéro de la vue
attr_accessor :number
end
Dans le contrôleur de notre application, nous devons mettre en place une action permettant d'intercepter le clic sur un item de la toolbar. L'item cliqué sera lui-même passé à cette action et nous indiquera quelle vue doit être affichée. Il faut également avoir, dans le contrôleur, l'information sur la vue courante. Enfin, afin de pouvoir récupérer le contentView de la fenêtre, nous devons déclarer un outlet pour cette dernière.
AppController.rb# AppController.rb
# CARB
#
# Created by greg on 26/03/10.
# Copyright 2010 Grégoire Lejeune. All rights reserved.
class AppController
attr_accessor :currentPreferenceView
attr_accessor :window
def clickPref(sender)
# TODO
end
end
Création de l'interface
Dans Interface Builder, commencez avant tout par ajouter un objet de type AppController. Ajoutez ensuite la toolbar dans la fenêtre (NSToolbar) et modifiez la de façon à obtenir quelque chose qui ressemble à cela :

Vous devez ensuite lier chaque item (NSToolbarItem) de la toolbar avec l'action clickPref:. Pour que dans l'action nous sachions quelle vue nous voulons afficher, nous allons utiliser l'identifieur de l'item. Pour cela, pour chaque item, modifiez, dans les attributs, le paramètre Identifier en lui mettant comme valeur le numéro de la vue à afficher.

Ajoutez ensuite trois vues en précisant qu'elles sont de type PreferenceView :

Pour chaque vue, positionnez, via les User Defined Runtime Attributes, la valeur de son numéro :

Il faut enfin, pour chaque vue, créer les liens. Ainsi :
- La vue une aura comme nextPreferenceView la vue deux.
- La vue deux aura comme nextPreferenceView la vue trois et comme previousPreferenceView la vue une.
- La vue trois aura comme previousPreferenceView la vue deux.
Pensez enfin à personnaliser comme il vous convient le contenu de chaque vue. Il faut aussi lier l'outlet currentPreferenceView a la vue que vous souhaitez voir afficher au démarrage de l'application (dans mon cas j'ai opté pour la vue une) et l'outlet window à la fenêtre.
Mise en place de l'animation
Revenons dans Xcode. Avant de coder notre animation, nous devons ajouter le framework QuartzCore dans la liste des frameworks utilisés par notre application. Pour cela, faites un clic droit sur l'entrée Add > Existing Frameworks... du menu contextuel de l'entrée Linked Frameworks de la zone Groups and Files. Dans le panneau qui s'affiche, sélectionnez QuartzCore.framework et cliquez sur Add.
Nous allons maintenant ajouter le code en déroulant le fonctionnement de notre application.
Lors du démarrage, si nous ne faisons rien, aucune vue n'est affichée. Nous devons donc ajouter la vue attachée à currentPreferenceView comme subview1 de la vue principale (contentView) de la fenêtre :
AppController.rb# AppController.rb
# CARB
#
# Created by greg on 26/03/10.
# Copyright 2010 Grégoire Lejeune. All rights reserved.
class AppController
attr_accessor :currentPreferenceView
attr_accessor :window
def awakeFromNib
contentView = self.window.contentView
contentView.setWantsLayer(true)
contentView.addSubview(currentPreferenceView)
end
def clickPref(sender)
# TODO
end
end
Nous pouvons ensuite définir comment doit se faire la transition entre les subviews de la vue principale. Ceci se fait en lui ajoutant une animation via CATransition.animation. Pour définir l'effet, nous utilisons setType: en lui passant en paramètre le type de l'effet. Il en existe quatre. Pour un slide, nous devons utiliser kCATransitionPush. Personnellement je n'ai pas trouvé comment obtenir l'équivalent avec MacRuby, je l'ai donc définie sous forme de constante (KCATransitionPush). Nous aurons aussi besoin de définir un sous-type indiquant le sens du slide. Pour cela nous avons également quatre possibilités pour lesquelles, une fois encore je n'ai pas trouvé l'équivalent avec MacRuby et je les ai donc redéfinies (tout au moins les 2 que nous utiliserons : KCATransitionFromLeft et KCATransitionFromRight). Nous définissons le sous-type de la transition en utilisant setSubtype:. Nous pouvons terminer en positionnant cette transition comme animation pour les subviews de la vue principale de la fenêtre.
AppController.rb# AppController.rb
# CARB
#
# Created by greg on 26/03/10.
# Copyright 2010 Grégoire Lejeune. All rights reserved.
KCATransitionPush = "push"
KCATransitionFromLeft = "fromLeft"
KCATransitionFromRight = "fromRight"
class AppController
attr_accessor :currentPreferenceView
attr_accessor :window
def awakeFromNib
contentView = self.window.contentView
contentView.setWantsLayer(true)
contentView.addSubview(currentPreferenceView)
@transition = CATransition.animation
@transition.setType(KCATransitionPush)
@transition.setSubtype(KCATransitionFromLeft)
ani = NSDictionary.dictionaryWithObject(@transition, forKey:"subviews")
contentView.setAnimations(ani)
end
def clickPref(sender)
# TODO
end
end
Vous remarquerez que j'ai arbitrairement défini le sens de l'animation de la gauche vers la droite (KCATransitionFromLeft). Cela n'a aucune importance, car cette valeur sera modifiée quand nous effectuerons réellement la transition entre vues.
Nous avons choisi de lier clickPref: comme action de chaque clic sur les items de la toolbar. Pour savoir quelle vue doit être affichée, nous avons choisi de stocker l'information dans l'identifiant de l'item. Nous pouvons donc récupérer, non seulement le numéro de la vue courante, mais également le numéro de la vue à afficher :
# AppController.rb
# CARB
#
# Created by greg on 26/03/10.
# Copyright 2010 Grégoire Lejeune. All rights reserved.
KCATransitionPush = "push"
KCATransitionFromLeft = "fromLeft"
KCATransitionFromRight = "fromRight"
class AppController
# ...
def clickPref(sender)
# Vue a afficher
wantedViewNumber = sender.itemIdentifier.to_i
# Vue courante
currentViewNumber = currentPreferenceView.number.to_i
return if wantedViewNumber == currentViewNumber
end
end
Nous pouvons maintenant faire la transition entre vues. Pour cela, nous déterminons la direction à prendre selon que wantedViewNumber - currentViewNumber est inférieur ou supérieur à 0
# AppController.rb
# CARB
#
# Created by greg on 26/03/10.
# Copyright 2010 Grégoire Lejeune. All rights reserved.
KCATransitionPush = "push"
KCATransitionFromLeft = "fromLeft"
KCATransitionFromRight = "fromRight"
class AppController
# ...
def clickPref(sender)
# Vue a afficher
wantedViewNumber = sender.itemIdentifier.to_i
# Vue courante
currentViewNumber = currentPreferenceView.number.to_i
return if wantedViewNumber == currentViewNumber
direction = wantedViewNumber - currentViewNumber
if direction > 0
# de la droite vers la gauche
@transition.setSubtype(KCATransitionFromRight)
while( wantedViewNumber != currentViewNumber )
self.doAnimation(self.currentPreferenceView.nextPrefernceView)
currentViewNumber = currentPreferenceView.number.to_i
end
else
# de la gauche vers la droite
@transition.setSubtype(KCATransitionFromLeft)
while( wantedViewNumber != currentViewNumber )
self.doAnimation(self.currentPreferenceView.previousPreferenceView)
currentViewNumber = currentPreferenceView.number.to_i
end
end
end
end
Il ne nous reste plus qu'à écrire doAnimation:. C'est dans cette méthode que nous ferons réellement la transition. Pour cela nous utilisons replaceSubview:with: qui permet de dire, pour une animation, quelle vue doit être remplacée par quelle autre.
# AppController.rb
# CARB
#
# Created by greg on 26/03/10.
# Copyright 2010 Grégoire Lejeune. All rights reserved.
KCATransitionPush = "push"
KCATransitionFromLeft = "fromLeft"
KCATransitionFromRight = "fromRight"
class AppController
# ...
def doAnimation(newView)
contentView = self.window.contentView
contentView.animator.replaceSubview(currentPreferenceView, with:newView)
self.setCurrentPreferenceView(newView)
end
# ...
end
Je vous laisse raccorder les bouts, sachant que pour les plus flémards vous pouves toujours télécharger cet exemple.
1 Pardonnez moi l'utilisation de ce terme, mais je n'ose pas le traduire en sous-vue.