Cocoa.fr

Developpement Mac, Objective-C, Cocoa et Swift

Migration cocoa.fr

Un blog Mac qui garde l'esprit historique, avec une base moderne.

Theme modernise a partir de la palette d'origine. Architecture prete pour importer les anciens contenus Objective-C, Cocoa et Swift.

06 April 2009 Design

Mais que mettre dans les menus contextuels ?

L’un des béta-testeurs de mon logiciel m’a posé une question intéressante: “À quand les menus contextuels ?”. Ce à quoi j’ai répondu: “Jamais !”. Comment puis-je donner une réponse aussi catégorique ? Parce que cela fait un moment que j’y réfléchis.

Une demande des switchers

Les menus contextuels sont apparus sous Mac OS 8.5, alors que jusqu’alors, Apple ne les avait pas trouvés nécessaires. Il s’agissait de répondre à une demande des utilisateurs venant du monde Windows, qui ne parvenaient pas à créer un dossier ou faire du copier-coller.

Sous NeXTStep

Je souhaite revenir à ce vieux système, parce qu’il a poussé les menus contextuels très loin. D’après ce que je me suis laissé dire, un clic droit y permettait d’afficher un menu proposant toutes les opérations possibles sur l’objet cliqué, présentées en menus hiérarchiques. Et ce fonctionnement n’était pas affreux, puisqu’il était cohérent: on était sûr d’y trouver toutes les opérations possibles.

Il n’était pas non plus idéal, parce que les menus hiérarchiques ne sont jamais très pratiques: la souris a tendance à replier trop facilement le menu, et puis quand on arrive sur le bord droit de l’écran, l’article de menu doit passer à la ligne; on s’y perd un peu.

Pendant ce temps là, chez Microsoft

Sur PC, on était habitué depuis longtemps à ce que la souris comporte souvent trois boutons. Seules les applications de CAO en tiraient parti; pour le reste, comme l’IHM de Windows avait été pompée sur le Mac, Microsoft s’est longtemps demandé que faire des boutons supplémentaires, qui servaient à faire défiler le document ou à émuler le double-clic. Quand ils se décidèrent à utiliser le bouton droit pour faire apparaître un menu contextuel, ils ne voulurent pas — comme nous l’avons vu, par commodité — proposer trop de menus hiérarchiques, mais limitèrent la liste des opérations à une poignée.

Ceci amena alors une question: quelles opérations incorporer ?

Revenons au Mac

Tout développeur se retrouve donc avec la même question que Microsoft. Comme chez Apple, ils sont balèzes et que ça fait dix ans que ça existe sur Mac, ils ont dû trouver la réponse, pas vrai ? Comme à mon habitude, je m’en vais lire la bible, et la confronter aux logiciels d’Apple:

Un petit exemple sous Safari:

Copie d'écran de Safari

Et un extrait des AHIG:

Include a small subset of the most commonly used commands in the appropriate context. For example, Edit menu commands should appear in the contextual menu for highlighted text, but a Save or a Print command should not.

Un autre exemple sous Finder:

Copie d'écran de Finder

Always ensure that contextual menu items are also available as menu commands. A contextual menu is hidden by default and a user might not know it exists, so it should never be the only way to access a command. In particular, you should not use a contextual menu as the only way to access an advanced or power-user feature.

Ma démarche n’est pas de démontrer qu’Apple — qui n’est pas à une contradiction près — ne suit pas les règles qu’elle édicte; simplement que ses ingés ne savent foutrement pas quoi mettre dans ces menus.

C’est pas tout ça, mais j’ai un logiciel à coder

Je ne saisis pas bien l’intérêt d’avoir une manière supplémentaire de faire du copier-coller, dont les raccourcis-clavier sont bien connus. En tant qu’utilisateur, j’utilise les menus contextuels, mais justement pour les fonctionnalités avancées.

Et pour une application aussi simple que la mienne — il n’y a même pas de Préférences — je peux dire que les menus contextuels ne sont pas pour demain.

Renaud Pradenc Céroce

01 April 2009 iPhone / iPod Touch

Apple passe de Objective-C à Java

Apple vient d’annoncer son projet d’abandonner le langage Objective-C en faveur de Java pour un certain nombre de raisons :

  • Il est nettement plus facile de trouver des développeurs Java que des développeurs Objective-C.
  • Java est le seul vrai langage pour écrire des applications Enterprise
  • La compatibilité des application iPhone avec les applications Android (qui utilise déjà Java).

Le SDK iPhone devrait donc rapidement apparaître dans une nouvelle version permettant l’utilisation de Java, et d’après les rumeurs, une version 4.0 de XCode basé sur Eclipse devrait être remise aux développeurs lors de la WWDC 2009.

31 March 2009 En vrac

En vrac : iPhone

Après quelques jours avec peu de publication sur Cocoa.fr, voici un billet particulièrement fourni pour la série En vrac. On commence par un coup de gueule et une interview de Cyril, le développeur de iCompta et iComptaMobile :

Les articles suivants sont aussi très intéressant :

Et pour les personnes intéressées par des articles plus technique, je vous invite à lire :

Pour finir, vous trouverez sur le MacDev Network un retour sur le dernier livre concernant de développement Mac de The Pragmatic Programmers :

27 March 2009 Conférences

WWDC 2009 : du 8 au 12 juin

Apple vient d’annoncer les dates de l’édition 2009 de la WWDC, et elle aura lieu du 8 au 12 juin au Moscone Center de San Francisco. Elle proposera aux développeurs de suivre diverses conférences concernant l’iPhone et Mac OS X et de rencontrer les ingénieurs Apple.

Elle a eu lieu l’année dernière à guichet fermé, il y a donc de fortes chances que les places partent très vite à nouveau cette année. Pour en savoir plus et s’inscrire :

17 March 2009 Conférences

Les nouveautés de l’iPhone OS 3

Apple vient de présenter les nouveautés de la version 3.0 de l’iPhone OS et on peut dire qu’il y a des points vraiment sympatique pour les développeurs et les utilisateurs :

  • Attendu depuis longtemps, le copier / coller arrive sur iPhone et fonctionne de la manière suivante :
    • On tape deux fois sur un mot pour le sélectionner. Une fois un mot surligné, on peut modifier les bords de la sélection pour séléctionner un bloc.
    • On secoue l’iPhone pour annuler une sélection ou rétablir une sélection annulé.
    • Il est possible d’effectuer copier / coller d’une application à une autre.
  • 1000 nouvelles API pour les développeurs.
  • Possibilité de proposer à la vente du contenu dans la application. Par exemple de vendre des niveaux dans un jeu, des ebooks pour une application du genre Kindle.
  • Gestion des jeux multi-joueurs ou de l’échange de données entre iPhone avec une auto-détection des iPhone proches (en utilisant le protocole Bonjour sur une connexion Bluetooth).
  • Possibilité de communiquer avec les accessoires externes Made for iPod par le connecteur 30 broches présent sous l’iPhone.
  • En pour finir, l’arrivé du support des applications Push (qui nécessiterait beaucoup moi de batterie que les taches de fond). Cela fonctionnera de la manière suivante :
    • quand l’application est en ligne elle reçoit les données directement depuis le serveur.
    • quant elle ne tourne pas, les serveurs d’Apple font office de mandataire.

Vous trouverez tous les informations utile, et la possibilité pour les développeurs de télécharger la version beta sur l’iPhone Dev Center :

12 March 2009 iPhone / iPod Touch

iPhone 3.0 : Apple lance les invitations

Apple vient de lancer des invitations pour le 17 mars concernant la sortie d’une future version 3.0 du système d’exploitation de l’iPhone. Il sera intéressant de voir les nouveautés de cette version, tout particulièrement en ce qui concerne les programmes en taches de fond. J’essayerais bien sur de suivre les retranscriptions de cette conférence de presse et de faire un résumé des informations intéressantes pour les développeurs.

Get an advance preview of what we're building

11 March 2009 Pas à pas

Générateur d’images fractales (8)

À force de jouer avec le générateur, quelque chose me chagrine: un nouvel utilisateur ne saurait même pas qu’il peut agir sur la vue à la souris. Nous allons donc modifier son pointeur pour apporter cette information.

Comportement voulu

Voici le fonctionnement que j’attends:

  • Fèches haut/basQuand on appuie sur la touche Contrôle, le curseur de la souris doit se transformer en flèches. Ceci, dès que le pointeur se trouve dans la vue.
  • Main ouverte Sinon, si le bouton n’est pas appuyé, le curseur est une main ouverte.
  • Main ferméeS’il est appuyé, une main fermé.

Changer le pointeur

Je vais déjà vous expliquer comment fonctionne ce système de curseurs sous Cocoa, la doc d’Apple me semblant assez indigeste pour quelque chose d’aussi simple.

La classe NSCursor dispose d’un ensemble de méthodes de classes qui renvoient un nouvel NSCursor, ayant des apparences diverses (croix, main, flèches, etc.). Fixer le pointeur est aussi simple que ceci:

[[crosshairCursor] set];

Qui donnera une apparence de croix au pointeur.

Cependant, NSCursor possède un mécanisme plus élaboré, basé sur une pile:

  • Quand une instance de NSCursor reçoit un appel à -[NSCursor push], cette instance devient le curseur courant, et l’ancien curseur est empilé.
  • Quand la classe NSCursor reçoit un appel à +[NSCursor pop], le curseur sur le haut de la pile est retiré et devient le nouveau curseur.

C’est ce mécanisme que nous allons utiliser, puisqu’il nous simplifie la vie, en mémorisant dans quel état il faut remettre le curseur.

Événements clavier

Il nous faut surveiller l’appui sur la touche Contrôle:

- (void)flagsChanged:(NSEvent *)theEvent
{
    if(pointeurDansLaVue)
    {

-[NSResponder flagsChanged:] est appelée dès qu’une touche spéciale est appuyée, que le pointeur soit dans la vue ou non. J’ai donc créé une variable d’instance pointeurDansLaVue. Nous fixerons sa valeur dans les méthodes indiquant l’entrée ou la sortie du pointeur de la vue.

        if([theEvent modifierFlags] & NSControlKeyMask)

On regarde si la touche Contrôle est appuyée.

            [[NSCursor resizeUpDownCursor] push];

On change le curseur (en mémorisant l’ancien).

        else
            [NSCursor pop];

Sinon, Contrôle n’est plus appuyée: il faut rétablir le curseur précédent.

    }
}

Il faut également rajouter ce code:

- (BOOL) acceptsFirstResponder
{
    return YES;
}

Autrement, nous ne recevrions pas les événements clavier.

Entrée et sortie de la vue

Tracking Area

Cocoa propose de surveiller la position du pointeur par le biais de la classe NSTrackingArea, qui représente un rectangle de la vue. Ajoutons les variables d’instance:

@interface CFRFractalView : NSView {
    IBOutlet CFRMandelbrotRender* render;

    NSTrackingArea* trackingArea;
    BOOL pointeurDansLaVue;
}

Et initialisons-les:

- (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self)
    {
        trackingArea = nil;
        pointeurDansLaVue = NO;
    }
    return self;
}

Apple conseille de créer les tracking areas lorsque la vue est insérée dans la fenêtre:

- (void)viewDidMoveToWindow
{
    trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
        options:(NSTrackingMouseEnteredAndExited
        | NSTrackingInVisibleRect | NSTrackingActiveInKeyWindow)
        owner:self
        userInfo:nil];

Nous fournissons à la méthode d’initialisation:

  • le rectangle définissant la tracking area Nous lui passons le rectangle définissant la vue. Il s’agit du rectangle de départ, la tracking area changeant de dimensions lors des redimensionnements de la vue.
  • Des options, combinées par l’opérateur OU:
    • NSTrackingMouseEnteredAndExited indique que nous voulons que les méthodes -mouseEntered: et -mouseExited: soient appelées lors de l’entrée, respectivement de la sortie du pointeur.
    • NSTrackingInVisibleRect nous permet de ne pas nous préoccuper de redimensionner la tracking area.
    • NSTrackingActiveInKeyWindow dit de ne surveiller le pointeur que quand la fenêtre est active.
  • owner est le propriétaire de la tracking area. Il est possible de déléguer cette gestion, mais ici c’est notre vue qui s’en occupe.
  • userInfo permet de passer des informations à l’intérieur du NSEvent reçues par les méthodes d’événement. Inutile pour nous.
    [self addTrackingArea:trackingArea];
    

    }

Nous associons ensuite la tracking area à la vue.

Événements mouseEntered et mouseExited

- (void)mouseEntered:(NSEvent *)theEvent
{
    [[NSCursor openHandCursor] push];
    pointeurDansLaVue = YES;
}

Quand le pointeur entre dans la vue, nous changeons le curseur en une main ouverte, et mettons à jour pointeurDansLaVue, nécessaire à la méthode -flagsChanged: décrite plus haut.

- (void)mouseExited:(NSEvent *)theEvent
{
    pointeurDansLaVue = NO;
    [NSCursor pop];
}

Quand le pointeur sort de la vue, nous rétablissons le curseur précédent, a priori la flèche normale.

Événements mouseDown et mouseUp

- (void)mouseUp:(NSEvent *)theEvent
{
    [NSCursor pop];
}

Lorsque le bouton de la souris est relâché, nous rétablissons le curseur précédent.

Pour les clics:

- (void)mouseDown:(NSEvent *)theEvent
{

Lors d’un clic…

    if([theEvent modifierFlags] & NSControlKeyMask)
    {

… si la touche Contrôle est appuyée…

        [[NSCursor resizeUpDownCursor] push];

Utiliser un curseur en formes de doubles-flèches. À vrai dire, ce curseur est déjà le curseur actuel (puisque la touche Contrôle a été enfoncée avant le clic), mais cet appel permet de contrebalancer le [NSCursor pop] de la méthode -mouseDown:…

    }
    else
    {
        [[NSCursor closedHandCursor] push];

… appel lui même nécessaire pour contrebalancer cet appel à adopter un curseur en main fermée.

    }
}

Nous en avons fini avec ces histoires de curseurs. Je vous accorde que ça semble un peu bricolé, mais on ne peut guère faire autrement.

Résultat

Il n’y a plus qu’à essayer: ça fait tout de suite beaucoup plus pro !

Version de Mac OS X

La classe NSTrackingArea fut introduite avec Mac OS 10.5. Autrefois, on utilisait le mécanisme des tracking rectangles, qui est similaire mais plus compliqué à mettre en œuvre. Toujours est-il que nous perdons la compatibilité avec les anciens systèmes, alors prévenons leurs utilisateurs avec un joli message d’erreur. Pour cela:

  • Clic droit sur Info.plist > Open As > Plain Text File
  • avant la balise </dict> finale, insérez: <key>LSMinimumSystemVersion</key> <string>10.5.0</string>

À bientôt pour la suite.

Le projet XCode complet à télécharger.

Renaud Pradenc Céroce.com

05 March 2009 Pas à pas

Générateur d’images fractales (7)

Aujourd’hui nous allons permettre — enfin ! — de se déplacer dans l’ensemble de Mandelbrot.

Retirer la mesure de performance

Commencez par ouvrir MyDocument.xib et retirez l’instance de CFRMesurePerf qui ne nous est plus nécessaire.

L’utilisateur naviguera de la façon suivante:

  • se déplacer sur le repère se fait en glissant la souris avec le bouton principal (gauche) appuyé.
  • le zoom se fait grâce à la molette de la souris, ou pour les souris qui n’en seraient pas pourvues, en maintenant la touche Contrôle appuyée, et en glissant la souris avec le bouton appuyé.

Le déplacement

Rappelez-vous que la classe CFRMandelbrotRender contient une variable d’instance centre:

Complexe_t centre;  // Centre du repère

De fait, se déplacer dans l’ensemble consiste à déplacer le centre du repère, à recalculer la bitmap et à l’afficher.

Glissé de la souris

Je vous rappelle que NSView hérite de NSResponder. Or, la méthode -[NSReponder mouseDragged:] est appelée lorsque la souris est glissée sur la vue, avec le bouton principal appuyé :

- (void)mouseDragged:(NSEvent *)theEvent
{

    // Déplacer le centre de la vue
    [render decalerCentreX:-[theEvent deltaX]
                        y:[theEvent deltaY]];

Je vais revenir tout de suite sur la méthode decalerCentreX:y:. Les méthodes -[NSEvent deltaX] et -[NSEvent deltaY] permettent d’obtenir le nombre de points dont s’est déplacé le pointeur de la souris pendant le glissé.

    [self setNeedsDisplay:YES];
}

Cette méthode demande à la vue se réafficher dans la prochaine boucle d’affichage : la méthode -drawRect: sera appelée à nouveau.

Décaler le centre

Venons-en à la méthode -[CRFMandelbrotRender decalerCentreX:y:]. Nous avons ici une conversion d’échelle à faire. En effet, les décalages lui sont exprimés en pixels, et il nous faut les exprimer en coordonnées “repère”:

- (void) decalerCentreX:(NSInteger)pixelsHoriz
                    y:(NSInteger)pixelsVerti
{
    double facteurReperePixel = largeur / largeurBitmap;

D’abord, nous calculons le rapport d’échelle entre la largeur du repère, et celle de la bitmap (en pixels, comme la vue).

    double deltaX = pixelsHoriz * facteurReperePixel;
    double deltaY = pixelsVerti * facteurReperePixel;

Nous appliquons alors ce facteur aux décalages en pixels pour obtenir les décalages en coordonnées “repère”.

    centre.reel += deltaX;
    centre.imag += deltaY;
}

Enfin, nous translatons le centre du repère.

Et voilà, lancez le programme, ça fonctionne.

Le zoom à la molette

Zoomer

Zoomer consiste à modifier la variable largeur de CFRMandelbrotRender, qui représente la largeur de l’intervalle de calcul:

double largeur;

Ainsi, si vous divisez la largeur par 2, vous zoomez de 200%; si vous multipliez la largeur par 2, vous dézoomez de 50%:

- (void) zoomerDuFacteur:(double)facteur
{
    largeur = largeur * facteur;

    // Corriger les valeurs extrêmes
    if(largeur < 0.0001)
        largeur = 0.0001;
    else if(largeur > 8.0)
        largeur = 8.0;
}

J’ai ajouté la correction des valeurs extrêmes après quelques essais . Les limites sont empiriques; d’ailleurs, nous changerons sans doute la limite basse un jour.

Gestion de la molette

Quand la molette est actionnée, la méthode -[NSResponder scrollWheel:] est appelée:

- (void)scrollWheel:(NSEvent *)theEvent
{
    // deltaY est > 0 quand la molette est tournée en avant.
    //  Il vaut +/- 0.1 pour un petit mouvement et +/- 10 pour un grand.
    double deltaY = [theEvent deltaY];

Il m’a fallu faire quelques essais pour régler l’amplitude du zoom. J’ai mesuré que tourner lentement la molette vers l’avant donnait un deltaY aux alentours de 0,1, et la tourner vite, autour de 10. deltaY est négatif quand on la molette est tournée vers l’arrière.

    [render zoomerDuFacteur: 1.0 + deltaY/20.0];

La division par 20 de deltaY est là encore empirique. Par exemple, en tournant rapidement la molette vers l’arrière, vous obtenez un facteur de zoom de l’ordre de 1+ (-10)/20) = 0.5, soit un zoom x2.

    [self setNeedsDisplay:YES];
}

Lancez le programme: ça zoome !

Le zoom par Contrôle + glissé

Modifions la méthode -mouseDragged:

- (void)mouseDragged:(NSEvent *)theEvent
{
    // La touche Contrôle est-elle appuyée ?
    if([theEvent modifierFlags] & NSControlKeyMask)
    {

Il nous faut d’abord savoir si la touche Contrôle est appuyée pour distinguer les deux type de glissés.

        // Zoomer/dézoomer
        double deltaRelatif = 2.0 * ([theEvent deltaY] / [self bounds].size.height);

Par soucis d’ergonomie, ce qui nous intéresse, n’est pas le déplacement absolu de la souris, mais son déplacement relatif. D’où le calcul de deltaY/(hauteur de la vue). J’ai ensuite ajouté un facteur 2, déterminé — vous l’aurez deviné — de façon empirique.

        [render zoomerDuFacteur: 1.0 + deltaRelatif];
    }
    else
    {
        // Déplacer le centre de la vue
        [render decalerCentreX:-[theEvent deltaX] y:[theEvent deltaY]];
    }
    [self setNeedsDisplay:YES];
}

Le reste a déjà été expliqué.

Le résultat

Lancez le programme, et admirez le résultat. Personnellement, je trouve l’affichage encore un peu lent.

En zoomant loin en avant, on reconnaît enfin la structure “fractale” de l’ensemble (le même motif se répétant à des échelles différentes).

Répétitions de la structure fractale

Zoom sur un détail Cependant, les détails ne sont alors plus très présents. C’est parce que nous avons fixé le nombre d’itérations maximales trop bas. Heureusement, nous permettrons prochainement de modifier ce seuil.

À bientôt.

Le projet XCode complet à télécharger.

Renaud Pradenc Céroce.com