Follow me
RSS feed
My sources
My Viadeo

Core Animation avec MacRuby

Greg | 26 Mar 2010

Dev 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 :

  1. 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.
  2. Nous faisons le glissement :
    • En passant à la vue suivante si n < m
    • En passant à la vue précédente si n > m
  3. 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 :

CoreAnimation

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.

CoreAnimation

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

CoreAnimation

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

CoreAnimation

Il faut enfin, pour chaque vue, créer les liens. Ainsi :

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.

Copyright © 2009 - 2011 Grégoire Lejeune.
All documents licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License, except ones with specified licence.
Powered by Jekyll.