Mouviciel

Création d'applications pour Mac OS X

Les coulisses d'un développeur Mac

Initiation à CoreAnimation — Interlude

Publié le vendredi 24 octobre 2008, un commentaire

Dans la première partie, nous avons installé la structure pour travailler avec CoreAnimation et nous avons affiché une image dans une CALayer. Avant de vous proposer de poursuivre avec ces bases en altérant l'image par le biais de CoreAnimation, je voudrais faire évoluer l'interface de l'application CoreAnimationTutorial de façon à ce qu'on puisse modifier divers paramètres sans avoir à recompiler.

En fait, cet article n'est pas vraiment spécifique de CoreAnimation. C'est plutôt une initiation à Interface Builder. Mais comme les prochaines étapes nécessiteront de toucher à l'interface graphique, je voudrais détailler la modification de l'interface une première fois afin d'être plus concis les fois suivantes.

Troisième figure — Un pupitre de contrôle

Pour commencer sur des bases communes, et après vous avoir laissé expérimenter les propriétés de placement (contentsGravity) et de cadrage (contentsRect) de l'image, je vous invite à modifier les dernières lignes de la méthode -awakeFromNib de la façon suivante :

    // Placement de l'image
    [[animationView layer] setContentsGravity:kCAGravityResize];

    // Cadrage de l'image
    [[animationView layer]
     setContentsRect:CGRectMake(0, 0, 1, 1)];
}

Cela nous ramène aux valeurs par défaut du placement et du cadrage de l'image.

Ensuite, nous allons ajouter sous l'image un pupitre de contrôle qui nous permettra de modifier les paramètres d'affichage sans recompiler. Cela se passe d'abord dans Xcode puis dans Interface Builder. L'objectif de ces préliminaires est d'arriver à une fenêtre qui ressemble à ceci :

Apparence de la fenêtre à la fin des préliminaires

Dans Xcode, ajoutez un IBOutlet et une IBAction dans le fichierAppController.h qui serviront à contrôler les boutons radio :

@interface AppController : NSObject {
    IBOutlet NSView * animationView;
    IBOutlet NSMatrix * gravityMatrix;
}

-(IBAction)gravityMatrixDidChange:(id)sender;

@end

Maintenant ouvrez MainMenu.xib dans Interface Builder.

Ajoutez un Radio Group (dans la palette Library, shift-cmd-L, rubrique Cocoa — Views & Cells — Inputs & Values) en bas à gauche de la fenêtre de l'application. Ajustez les propriétés de ce Radio Group de la façon suivante (cela se passe dans la palette Inspector, shift-cmd-I) :

Maintenant, changez le label de chacun des boutons radio : double-cliquez sur le label Radio et remplacez sa valeur par la valeur que je vous ai donnée dans l'image ci-dessus.

Mettez ce groupe de boutons radio dans une boîte : Vérifiez que le groupe est toujours sélectionné (le titre de la palette Inspector commence par Matrix), puis menu Layout — Embed Objects In — Box. Déplacez la Box à distance juste suffisante pour répondre aux préconisations d'Apple : des lignes pointillées bleues apparaissent lorsque c'est le cas. Changez le titre en Contents Gravity.

Dans la palette Inspector, rubrique Size de la boîte (son titre est Box Size) Ajustez l'Autosizing de façon à ce que la boîte ne change pas de dimensions et reste collée en bas au centre de la fenêtre :

Réglage Autosizing du Contents Gravity

Ajoutez maintenant une Custom View en haut de la fenêtre (dans la palette Library, shift-cmd-L, rubrique Cocoa — Views & Cells — Layout Views). Ajustez ses dimensions à Height: 216 et Width: 384. Placez-la en haut à gauche de la fenêtre sans laisser de marge avec le bord. Déplacez la boîte Contents Gravity juste sous la Custom View (jusqu'à ce que le trait bleu apparaisse). Ajustez la taille de la fenêtre pour être aussi large que la Custom View et juste assez haute pour laisser apparaître la boîte Contents Gravity (encore une fois avec le trait bleu). Le résultat doit être celui de la première image.

Ajustez l'Autosizing de la Custom View de la façon suivante pour indiquer qu'elle doit occuper tout l'espace dont elle dispose :

Réglage Autosizing de la Custom View

Sélectionnez la fenêtre en cliquant sur sa barre de titre. Dans palette Inspector, rubrique Size (titre Window Size), cochez la case Has Minimum Size et cliquez sur le bouton Use Current. Les dimensions minimales de la fenêtre devraient être 384 par 358.

Sauvez (cmd-S). Simulez (cmd-R). Les aspects graphiques sont prêts.

Maintenant, il s'agit de mettre en place les connexions entre les éléments graphiques et l'App Controller. Dans la fenêtre principale MainMenu.xib (English) faites ctrl-clic sur l'objet App Controller pour faire apparaître sa palette des connexions. Les éléments importants sont les deux Outlets animationView et gravityMatrix, ainsi que dans la rubrique Received Actions l'action gravityMatrixDidChange:.

À droite de chacun de ces éléments il y a un petit cercle qui devient un signe + lorsque la souris passe par dessus. Si vous cliquez et faites glisser la souris depuis un de ces cercles, une ligne bleue se dessine et suit le pointeur de la souris. Lorsque la souris passe au dessus d'un élément de l'interface graphique approprié, celui-ci est entouré d'un rectangle bleu. Si vous relâchez le bouton de la souris à cet instant, l'élément de l'App Controller est alors connecté à l'élément de l'interface graphique. Réalisez l'opération pour les trois couples d'éléments suivants :

Sauvez (cmd-S). Nous en avons fini avec Interface Builder.

Enfin, la dernière étape consiste à écrire l'action gravityMatrixDidChange: pour qu'elle modifie la propriété contentsGravity de la CALayer selon le choix que l'utilisateur aura saisi dans le groupe de boutons radio. Je vous propose le code suivant, à insérer dans AppController.m juste avant le @end :

-(IBAction)gravityMatrixDidChange:(id)sender
{
    NSDictionary *gravityNamesAndValues =
    [NSDictionary dictionaryWithObjectsAndKeys:
     kCAGravityCenter, @"Center",
     kCAGravityTop, @"Top",
     kCAGravityLeft, @"Left",
     kCAGravityBottom, @"Bottom",
     kCAGravityRight, @"Right",
     kCAGravityTopLeft, @"Top Left",
     kCAGravityTopRight, @"Top Right",
     kCAGravityBottomLeft, @"Bottom Left",
     kCAGravityBottomRight, @"Bottom Right",
     kCAGravityResize, @"Resize",
     kCAGravityResizeAspect, @"Resize Aspect",
     kCAGravityResizeAspectFill, @"Resize Aspect Fill",
     nil];
    
    NSString * gravity = [gravityNamesAndValues
                          objectForKey:[[sender selectedCell] title]];
    
    [[animationView layer] setContentsGravity:gravity];
}

@end

Compilez et lancez (cmd-R). Changez la gravité du contenu, redimensionnez la fenêtre. Voilà, nous avons le cadre qui nous permettra d'interagir avec CoreAnimation ! Bien sûr, pour l'instant nous ne pouvons manipuler que le placement de l'image. Mais je voulais entrer dans le détail de la construction d'une interface graphique de façon à être plus succint lorsque nous ajouterons d'autres paramètres à modifier.

Initiation à CoreAnimation — Première partie

Publié le vendredi 17 octobre 2008, aucun commentaire

CoreAnimation est un mécanisme de Leopard qui permet de donner du mouvement aux éléments de l'interface en exploitant le plus possible le processeur graphique. Le meilleur exemple est l'animation de TimeMachine.

J'écris cet article, et d'autres qui suivront, alors que je découvre CoreAnimation. Il s'agit d'une espèce de carnet de notes sur lequel j'enregistre ce que j'apprends. En le publiant ici j'espère que d'autres partageront cette initiation à CoreAnimation en même temps que moi.

D'autres articles existent sur le sujet. Il y a d'abord la documentation d'Apple, notamment le Core Animation Programming Guide qui peut être un bon point d'entrée au reste des documents officiels. Ensuite une série de cours sur CoreAnimation existe en français chez Saint Sylvain's Ranch. Enfin, plusieurs cas d'école sont disponibles en anglais chez Theocacao en particulier NanoLife et ArtGallery.

Première figure — Mettre CoreAnimation dans une fenêtre

Interface Builder propose un certain nombre de vues spécialisées, par exemple NSOpenGLView pour l'OpenGL, QTMovieView pour une séquence QuickTime, WebView pour un navigateur web, etc. Cependant, rien n'est proposé en standard pour CoreAnimation. Heureusement, la procédure n'est pas difficile.

Pour illustrer cela, je vous propose de créer un nouveau projet d'application Cocoa dans Xcode. Nommez-le CoreAnimationTutorial. Ajoutez une classe à ce projet : Menu File—New File..., puis Objective-C class. Nommez-la AppController.m et choisissez de créer aussi AppController.h. Il s'agit du contrôleur de l'application qui sera le cadre de nos expérimentations.

Ensuite, ouvrez le fichier qui décrit l'interface graphique MainMenu.xib. Nous allons y ajouter le controleur que nous venons de créer. Si ce n'est pas déjà fait, ouvrez la palette Library (cmd-shift-L) et la palette Inspector (cmd-shift-I).

Dans la palette Library trouvez l'élément Object de classe NSObject et faites-le glisser dans la fenêtre nommée MainMenu.xib (English). Cette fenêtre contient la description complète de l'interface graphique de l'application.

Assurez-vous que l'objet que vous venez de faire glisser est sélectionné, puis dans la palette Inspector choisissez l'onglet Identity. Dans la rubrique Class Identity Choisissez la classe AppController (c'est la classe que nous avons créée).

À ce stade, les fondations de notre application sont posées. Nous pouvons commencer les spécificités de CoreAnimation.

La base de CoreAnimation est la classe CALayer qui est analogue dans son rôle et sa structure à la classe NSView : Elle permet de faire des affichages à l'écran et de définir une arborescence d'objets CALayer. Concrètement, cela signifie que nous travaillerons dans des CALayer au lieu des NSView. Il faut donc trouver un moyen de placer une CALayer dans une fenêtre.

La technique est d'associer une CALayer à une des views de la fenêtre de l'application, grâce à la méthode setLayer: de NSView. Il s'agira de la racine de l'arborescence des CALayer. Ses dimensions sont celles de la view qui l'héberge. Voici la procédure.

D'abord, il faut que l'AppController ait connaissance de la view qu'on souhaite utiliser pour CoreAnimation. Cela se fait en deux étapes, la première dans Xcode et la deuxième dans Interface Builder. Pour la première étape, ajoutez une ligne de code à AppController.h :

//
//  AppController.h
//  CoreAnimationTutorial
//
//  Created by mouviciel on 03/10/08.
//  Copyright 2008 Mouviciel. All rights reserved.
//

#import <Cocoa/Cocoa.h>


@interface AppController : NSObject {
    IBOutlet NSView * animationView;
}

@end

Pour la deuxième étape, reprenez Interface Builder, puis ctrl-clic sur l'objet appController pour faire apparaître ses interfaces. Cliquez dans le petit cercle à droite de l'outlet animationView et faites glisser la souris en maintenant le bouton enfoncé jusqu'à la contentView de la fenêtre.

Maintenant que l'AppController a connaissance de la view à utiliser pour CoreAnimation, il reste à associer à cette vue une CALayer racine. Cela se passe dans Xcode : un framework à ajouter au projet et une méthode à ajouter à AppController.m. CoreAnimation est disponible via le framework QuartzCore. Il suffit de l'ajouter aux Frameworks déjà dans le projet (normalement, il y a seulement Cocoa dans Frameworks/Linked Frameworks). Pour cela, faites ctrl-clic sur Frameworks/Linked Frameworks puis choisissez dans le menu qui s'affiche Add—Existing Frameworks.... La fenêtre qui s'affiche vous propose de choisir dans le dossier Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks. Dans la liste, choisissez QuartzCore.framework.

Cela fait, vous pouvez modifier AppController.m de la façon suivante :

//
//  AppController.m
//  CoreAnimationTutorial
//
//  Created by mouviciel on 03/10/08.
//  Copyright 2008 Mouviciel. All rights reserved.
//

#import <QuartzCore/QuartzCore.h>
#import "AppController.h"


@implementation AppController

-(void)awakeFromNib
{
    // Ajout et activation d'une CALayer
    [animationView setLayer:[CALayer layer]];
    [animationView setWantsLayer:YES];
}

@end

Voilà, votre fenêtre est prête à recevoir des animations ! Pour l'instant ce n'est pas vraiment spectaculaire, mais la suite vous montre ce qu'on peut faire à partir de ça.

Deuxième figure — Afficher une image dans une CALayer

Dans son Core Animation Programming Guide, Apple indique qu'il y a trois façons d'ajouter du contenu à une CALayer :

Pour la présente initiation, je me contente d'aborder la première technique, à savoir fournir directement une image à la CALayer.

Pour créer une CGImageRef à partir d'un fichier ou d'une URL représentant une image, le plus simple est d'utiliser une CGImageSourceRef qui sert d'intermédiaire entre une CGImageRef et une URL :

-(void)awakeFromNib
{
    // Ajout et activation d'une CALayer
    [animationView setLayer:[CALayer layer]];
    [animationView setWantsLayer:YES];
    
    // Ajout d'une image dans la CALayer
    NSString * imagePath =
        @"/System/Library/CoreServices/DefaultDesktop.jpg";
    CFURLRef imageURL = (CFURLRef)[NSURL fileURLWithPath:imagePath];
    CGImageSourceRef imageSource =
        CGImageSourceCreateWithURL(imageURL, NULL);
    CGImageRef image =
        CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
    CFRelease(imageSource);
    [[animationView layer] setContents:(id)image];
}

Compilez et lancez l'application... Voilà ! Vous savez afficher une image qui occupe toute la fenêtre en se déformant si nécessaire. Si vous souhaitez éviter les déformations, vous pouvez modifier la valeur de la propriété contentsGravity. Par exemple, ajoutez les lignes suivantes à la fin de -awakeFromNib :

    [[animationView layer] setContents:(id)image];

    // Placement de l'image
    [[animationView layer] setContentsGravity:kCAGravityResizeAspect];
}

Cette fois, l'image apparaît en entier et sans déformation, avec des marges sur deux des côtés lorsque la fenêtre n'a pas les mêmes proportions que l'image. Si vous préférez supprimer les marges et acceptez que l'image soit tronquée, la valeur à donner est kCAGravityResizeAspectFill. La valeur par défaut, qui donne le comportement que nous avons observé au début, est kCAGravityResize.

Il est aussi possible d'afficher l'image dans sa dimension naturelle. Il suffit de donner à la même propriété contentsGravity une autre valeur que kCAGravityResize.... Les valeurs possibles spécifient une position :

Enfin, si vous ne désirez pas travailler l'image entière mais seulement avec une partie recadrée, vous pouvez modifier la valeur de la propriété contentsRect. Il s'agit d'un rectangle qui spécifie la portion de l'image à utiliser. Les valeurs qui définissent ce rectangle (abscisse et ordonnée de l'origine, largeur et hauteur du rectangle) sont exprimées en fractions de la taille de l'image, c'est-à-dire entre 0 et 1. Essayez par exemple d'ajouter ces lignes à la fin de -awakeFromNib :

    // Placement de l'image
    [[animationView layer] setContentsGravity:kCAGravityResizeAspect];

    // Cadrage de l'image
    [[animationView layer]
     setContentsRect:CGRectMake(0.3, 0.2, 0.3, 0.3)];
}

À suivre...

Avec cet exercice, nous avons fait un tout premier pas dans CoreAnimation. Comme je l'ai déjà dit, il s'agit d'une auto-initiation que je souhaite partager avec vous. La prochaine étape consistera à modifier d'autres paramètres de l'image affichée et de voir comment CoreAnimation assure la transition progressive d'un réglage à l'autre.

Contrôle de versions et Xcode — Deuxième partie

Publié le dimanche 5 octobre 2008, 3 commentaires

Dans la première partie sur le contrôle de versions, j'avais parlé des bénéfices qu'un développeur pouvait tirer du contrôle de versions en général. Dans cette partie j'entre dans le concret pour vous montrer les réglages à faire afin d'utiliser Subversion sur un projet Xcode.

Pourquoi Subversion avec Xcode ?

J'ai conclu la première partie en indiquant qu'il existait de nombreux outils de contrôle de version et que ce choix se limitait à trois dès qu'on travaillait avec Xcode :

Ainsi, les critères qui ont guidé mon choix de Subversion sont l'intégration dans Xcode, la disponibilité dans Leopard et la combinaison de maturité et de modernité. Il est temps maintenant de passer aux choses concrètes.

Créer un dépôt Subversion

Le principe de fonctionnement de Subversion est le suivant. Quelque part sur votre disque dur ou ailleurs existe un dépôt Subversion qui contient l'historique de tout votre code. Le développeur commence son travail en allant chercher dans le dépôt une copie du code qu'il souhaite modifier, c'est l'opération de checkout. Cette copie est placée dans un dossier qui joue le rôle d'espace de travail privé. Lorsque les modifications sont finies, le développeur les ajoute au dépôt grâce à l'opération de commit.

En général il est utile que plusieurs développeurs aient accès au dépôt. C'est pourquoi la plupart du temps ce dépôt est disponible via un serveur Subversion ; divers protocoles sont possibles, y compris http notamment via Apache. Je n'ai pas envie d'aborder ici l'installation d'un serveur Subversion, mon propos est d'illustrer le client Subversion intégré à Xcode. Mais pour cela j'ai tout de même besoin d'un dépôt Subversion. Le plus simple est donc d'en créer un sous forme d'un dossier sur votre disque dur. Cela se passe dans le Terminal, la commande est la suivante (le dépôt est créé dans ~/Subversion) :

svnadmin create $HOME/Subversion

Une autre solution presque aussi simple est de créer un dépôt Subversion auprès d'un hébergeur spécialisé. Parmi ceux qui ont une offre gratuite, il y a myVersionControl que je ne connais pas et Beanstalk que j'utilise régulièrement sans souci.

Une dernière précision : Un dépôt Subversion peut contenir plusieurs projets, il n'est pas nécessaire de créer un dépôt par projet.

Préparer Xcode

La préparation de Xcode nécessite deux opérations distinctes. La première opération consiste à informer Xcode de l'existence du dépôt Subversion que vous venez de créer.

Pour cela, choisissez le menu SCM—Configure SCM Repositories.... La fenêtre qui s'affiche est la rubrique SCM des préférences de Xcode. Choisissez le premier onglet, nommé Repositories, puis cliquez sur + en bas de la première colonne, intitulée elle aussi Repositories. Donnez un nom à votre dépôt, par exemple monDepotSubversion, puis choisissez Subversion pour SCM System. Enfin dans le champ URL: tapez le chemin d'accès au dépôt que vous venez de créer, par exemple chez moi c'est :

file:///Users/mouviciel/Subversion

Si tout se passe bien, vous devriez voir en bas du formulaire une bille verte annonçant Authenticated.

Pour la deuxième opération de préparation, j'aimerais attirer votre attention sur le fait que seules les sources sont à gérer par un système de contrôle de versions. J'utilise le terme de source dans le sens général de quelque chose à l'origine d'autre chose : cela recouvre plus que le code source et inclut toutes les ressources d'une application comme les icônes, les fichiers de configuration, les Makefiles, ... C'est-à-dire tout ce qui est nécessaire pour reconstruire l'application et seulement cela. En particulier les exécutables de l'application ne doivent pas être insérés dans le contrôle de version. Dans Xcode, il s'agit du dossier build d'un projet. Il faut donc trouver un moyen de l'exclure du contrôle de version.

Il s'agit d'une simple préférence de Xcode. Par défaut, celui-ci place le dossier build à l'intérieur du dossier du projet. Mais il est possible de choisir un autre emplacement, soit pour tous les dossiers build de tous les projets, soit pour un projet spécifique :

Préparer le projet

Ici, il s'agit de préparer l'arborescence du projet pour qu'elle contienne tout ce qu'on souhaite importer dansSubversion et seulement cela. Si votre projet n'existe pas encore, il vous suffit de le créer et de sauter le paragraphe qui suit. Sinon il y a peut-être un peu de ménage à faire.

En effet, même si vous avez modifié l'emplacement du dossier build, cela ne prendra effet qu'avec la prochaine compilation. Le dossier build qui était présent dans votre projet existe toujours. Il est donc nécessaire de le supprimer avant d'importer le projet dans Subversion. Pour cela, il vous suffit de le glisser dans la corbeille.

La deuxième étape de la préparation du projet vise à respecter l'usage recommandé par Subversion : un projet est un dossier qui contient à son tour trois autres dossiers nommés trunk (le tronc commun de votre projet, c'est-à-dire tout le contenu actuel de votre dossier de projet), branches (les diverses branches de développements, pour corriger un bug ou ajouter une fonctionnalité, ...) et tags (les versions référencées de votre application que vous souhaitez retrouver rapidement).

Pour cela, le Finder suffit. Dans votre dossier de projet, créez un dossier trunk et glissez-y tous les fichiers et dossiers du projet. Ensuite, créez les dossiers branches et tags à côté de trunk.

Importer le projet

Les opérations relatives au contrôle de versions sont réalisées dans Xcode via la fenêtre qui s'affiche en sélectionnant le menu SCM—Repositories. Dans la colonne Repositories à gauche, choisissez votre dépôt monDepotSubversion, puis cliquez sur l'outil Import à gauche dans la barre d'outils.

Localisez votre projet, le dossier qui contient trunk, branches et tags, ajoutez un commentaire qui décrit ce que vous êtes en train de faire, par exemple :

monSuperProjet: Importation du projet dans le dépôt Subversion.

Créer l'espace de travail

Voilà, le projet est importé ! Maintenant il reste une dernière précaution à prendre : le dossier du projet que vous venez d'importer n'est pas un espace de travail Subversion. Même après importation, il reste un dossier simple. Pour créer un espace de travail Subversion il faut récupérer le dossier depuis le dépôt Subversion par une opération de checkout.

D'abord, pour éviter de vous embrouiller entre le dossier simple et l'espace de travail, je vous recommande de glisser le dossier simple à la corbeille. N'ayez pas d'inquiétude, tout est dans le dépôt. Ensuite, je vous suggère de ne récupérer que le tronc commun du projet, le trunk. Voici la marche à suivre.

Dans Xcode, choisissez le menu SCM—Repositories, puis votre dépot Subversion dans la colonne de gauche. Dans le navigateur multicolonnes, choisissez le dossier de votre projet, puis le dossier trunk à l'intérieur de celui-ci. Enfin, cliquez sur l'outil Checkout de la barre d'outils. Choisissez l'emplacement de l'espace de travail de votre projet, nommez-le du nom de votre projet plutôt que la proposition trunk. Finissez l'opération en cliquant sur le bouton Checkout puis sur Open de la fenêtre modale suivante.

Il reste un tout petit détail d'importance : modifier les préférences du projet pour le lier au dépôt Subversion. Affichez les préférences du projet via le menu Project—Edit Project Settings, puis sélectionnez l'onglet General. Presque en bas de la fenêtre, il y a une liste déroulante intitulée SCM Repository: dont la valeur est None. Il suffit de changer cette valeur pour le nom du dépôt d'où vient le projet. Normalement il est indiqué avec un —Recommended à la fin.

Voilà ! vous pouvez commencer à travailler dans votre projet géré en contrôle de versions.

Enregistrer les modifications

La dernière opération, celle que vous utiliserez régulièrement, consiste à enregistrer les modifications au fur et à mesure de votre développement. Pour ma part, j'utilise le menu SCM—Commit Entire Project... qui ouvre une fenêtre dans laquelle je peux saisir le commentaire de commit. En général je décris de façon plus ou moins détaillée sur quoi ont porté les nouvelles modifications.

Récapitulons...

Voilà donc les diverses manipulations qui vous permettent d'utiliser Subversion dans Xcode. Je les reprends ici :

Maintenant, je vous laisse vous familiariser avec tout cela. Je vous invite à consulter la documentation de Subversion pour aller plus loin dans votre prise en main. Et bien sûr, les commentaires sont les bienvenus !

Archive

Rubriques

Abonnement