Site Perso de

Thomas JANNAUD

Des actualités personnelles sous un style impersonnel, et inversement.



Tutoriel Cocoa / Objective-C
Bases pour créer une appli en Cocoa/Obj-C avec interface + astuces et code sources 14 Mai 2009
programmation

Cocoa est le framework phare d'Apple pour la programmation. Il n'est pas aussi simple à prendre en main que le C++ ou le Java mais on peut faire de très beaux programmes avec. Les difficultés viennent de la gestion de la mémoire, et des tas de petites bidouilles qu'il faut faire constamment pour avoir un programme qui respecte le modèle 3-tiers que nous impose Apple. Et aussi du peu de ressources sur internet, en comparaison avec Java ou C++. Il ne m'aura vraiment pas été rare de passer 3 ou 4 heures à chercher comment faire quelque chose qui ne prend en fait qu'une ligne (mais laquelle !), comme la détection de l'événement mouseMove.

Les avantages de Cocoa sont : l'accès à une interface graphique qui permet de faire de très jolis programmes, dans la plus pure tradition Apple. Et aussi de nombreux frameworks qui rendent la vie très simple au programmeur ; citons entre autres la sérialisation (pour ouvrir/sauvegarder simplement des choses au sein des logiciels), l'opération Annuler/Rétablir, ...

Vous trouverez dans cet ouvrage de quoi apprendre Cocoa et Objective-C par l'exemple... sans doute le meilleur moyen pour appréhender un langage. Vous aurez dans ce livre à construire des petites applications simples, pas à pas, que vous allez faire grossir et ce faisant vous découvrirez d'innombrables techniques et principes pour coder proprement en Cocoa / Obj-C.
D'autre part, par expérience, vous verrez qu'un site internet ne remplacera jamais un livre papier.

Astuce

Dans XCode, si vous appuyez sur Alt et que vous double cliquez sur un mot, alors la fenêtre avec la documentation s'ouvre.

Liens avec C / C++

Cocoa est un drôle de mix entre le C et le C++, et ne ressemble finalement à aucun d'eux, à part dans leurs grandes idées. Il est néanmoins possible d'écrire du C (tout le temps) et du C++ si vous donnez à votre fichier source l'extension .mm. Si vous déclarez une fonction C ou C++ cela doit se faire à l'extérieur d'une balise @implementation

Cocoa semble aussi plus récent que le C / C++ au sens où l'on peut créer des classes ou appeler des fonctions à partir de chaînes de caractères, ou savoir si un objet est de la classe A ou de la classe B ou si c'est un descendant de C, ... On peut plus ou moins faire ça en C / C++ mais de manière très "sale", et je pense qu'avec la puissance des ordinateurs d'aujourd'hui avoir un code qui tourne 5% moins vite ça ne change rien, par contre avoir du code plus lisible, plus propre, et qui permet de coder 2 fois plus vite c'est plus important.

Petites subtilités

Appels de fonction

Les objets en Cocoa dérivent de la classe NSObject. si l'objet toto a une fonction f, il faut faire [toto f] pour appeler cette fonction, au lieu du traditionnel toto.f(). Pour les fonctions prenant des arguments, c'est [toto f:x:y:autreObjet]. Il y a aussi des fonctions qui exigent que les arguments soient nommés. Ainsi si on a une fonction définie comme

-(void) f:(int) hauteur largeur:(int) y pere:(A*)p {...}
...
[toto f:x largeur:y pere:autreObjet];

Structures

On accède à leurs éléments par '.' :

NSPoint pt = NSMakePoint(2.2, 0.5);
double a = pt.x + pt.y;

ou aussi (puisque les structures sont des éléments assez simples :

NSPoint pt;
pt.x = 2.2;
pt.y = 0.5;

A ma connaissance, on ne se sert surtout que de NSPoint, NSSize (construire avec NSMakeSize(largeur, hauteur), attributs width et height), et NSRect (construire avec NSMakeRect(x, y, largeur, hauteur), attributs NSPoint origin, et NSSize size).

L'opérateur '.' s'applique enfin aussi aux membres des objets, et il ne s'applique à rien d'autre. Dans l'exemple ci-dessous, C* c = ...; c.zPublique = 3;.
En Cocoa, tous les objets sont des pointeurs.

Classes

Déclaration typique d'une classe. Ici C est une classe qui dérive de B. B est soit NSObject soit une classe qui dérive de qqchose qui dérive de qqchose... qui dérive de NSObject.
Code de C.h

#import <Cocoa/Cocoa.h>

@class A, B;

@interface C : B {
int xPrive, ySemiPublique, zPublique
A* monA;
}

@property (readonly) A* monA;
@property (readonly) int ySemiPublique;
@property int zPublique;

-(id) init:(A*) aa;
-(void) fonctionPublique1;
-(NSString*) fonctionPublique2;

@end

Code de C.m

#import "C.h"
#import "A.h"
#import "B.h"

@interface C (PrivateMethods)
-(double) fonctionPrivee1;
-(A*) fonctionPrivee2:(int) a:(B*) b;
+(int) fonctionStatiquePublique; @end

@implementation C
@synthesize monA, ySemiPublique, zPublique;

-(double) fonctionPrivee1 {
...
}

-(A*) fonctionPrivee2:(int) a:(B*) b {
return [[A alloc] init:[b uneFonction]];
}

-(void) fonctionPublique1{
double a = [self fonctionPrivee1];
...
}

-(NSString*) fonctionPublique2 {
...
}

+(int) fonctionStatiquePublique {
...
}

-(id) init:(A*) aa {
if (self = [super init]) {
monA = [aa retain];
}
return self;
}

-(void) dealloc {
[monA release];
[super dealloc];
}

Explications

id
Toutes les classes devraient être dérivées de NSObject, même si ce n'est pas obligatoire. Dans ce cas, id serait un synonyme de NSObject* sinon c'est un pointeur sur n'importe quoi.
dealloc
C'est le destructeur d'un objet.
property et synthesize
Comme vous le verrez, on n'utilise jamais le '.' comme en C++ pour appeler des fonctions, comme dans monPerso.sauter(), mais on a quand même monPerso.age = 3, à condition de déclarer age en property et en synthesize. Le readonly n'est pas obligatoire, et on peut aussi mettre des choses comme nonatomic, retain (pour les objets), ...

Gestion de la mémoire

Cocoa a un Garbage Collector (utilisez-le !!) mais sur iPhone on est obligé d'utiliser la gestion "manuelle" de la mémoire, et celle-ci n'est pas comme en C++. Chaque objet possède un compteur, indiquant le nombre d'objets qui le référencent. Si ce compteur tombe à 0, l'objet est désalloué.

Comme on peut le voir sur l'exemple ci-dessus, on fait retain pour incrémenter le compteur, et release pour le décrémenter.

Pour créer un objet, on le fait toujours par le biais de [[... alloc] init]. La fonction init peut prendre des arguments. alloc met automatiquement le compteur à 1 pour l'objet.

Quelques fois, on n'a pas vraiment besoin de faire retain sur un objet si 3 lignes plus tard on fait release dessus. On le fait plutôt quand un objet prend vraiment une référence sur un autre objet, de manière "permanente", comme par exemple un membre de classe.

Principaux objets

NSString
Les chaînes de caractères. On peut soit en créer directement avec @"toto" (ne pas oublier le signe '@' sinon tout plante), soit en créer avec [NSString stringWithFormat:@"bonjour %@, ça va ?", nom]; ; regarder la doc de la classe NSString pour voir toutes les possibilités.
NSNumber, NSInteger, ...
Comme on va le voir, les tableaux et autres choses réclament souvent des objets. Or les int, double, ... n'en sont pas. On les encapsule donc au besoin dans des NSNumber, ou NSInteger, ... c'est comme on veut.
Fonctionnement : NSNumber * n = [NSNumber numberWithInt:6], m = [NSNumber numberWithDouble:0.14 + [n doubleValue]];
NSMutableArray
Un objet qui combine les avantages des tableaux et ceux des listes : insertion/suppression d'un élément, et accès à n'importe quel élément par son index. Le NSMuttableArray ne contient que des objets (pas de int, double ou BOOL), et les objets qu'il contient n'ont pas besoin d'être tous du même type. retain est appliqué aux objets placés dans ce tableau, et release quand on les retire.
NSMutableDictionary
C'est l'implémentation Apple de la table de hashage. Elle ne contient que des objets (pas de int, double ou BOOL), et ils n'ont pas besoin d'être tous du même type. retain est appliqué aux objets qui y sont placés, et release à ceux qui y sont retirés.
Sélecteurs
Un sélecteur est un pointeur sur une fonction. Par exemple quand on ouvre une fenêtre spéciale où l'utilisateur doit répondre par "oui", "non" ou "annuler". Dès que l'utilisateur choisit, Apple doit nous envoyer un message pour nous dire quel est le choix. On passe donc un pointeur vers la fonction "quoi faire", et comme ça c'est cette fonction qui sera appelée.
NSLog
Ce n'est pas vraiment une classe, ça sert juste à afficher des choses à la console.
Fonctionnement : NSLog(@"x vaut %d", x);
NSTimer

Un NSTimer est un compte à rebourd. Il va sonner toutes les x secondes. On lui passe un sélecteur en paramètre (un pointeur sur une fonction) et à chaque "bip" du timer, le système appelle notre fonction. On peut décider de ne faire biper le timer qu'une fois puis il s'éteint, ou alors une infinité de fois.

NSTimer* chrono = [NSTimer timerWithTimeInterval:nbSecondes target:self selector:@selector(bip:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:chrono forMode:NSDefaultRunLoopMode];
[chrono fire];

-(void) die {
[chrono invalidate];
}

-(void) bip:(NSTimer*)theTimer {
...
}

On peut énumérer rapidement les conteneurs avec des commandes telles que :
for(MonObjet* i in tableau) ...

Interface Builder

Choses à savoir.

Exemple de classe Controleur :

#import <Cocoa/Cocoa.h>

@interface Controleur : NSObject {
IBOutlet NSTextField* zoneTexte;
IBOutlet NSButton* boutonOk;
}

-(IBAction) clic:(id) sender;

@end

making an outlet with Interface Builder

Structure d'une application Cocoa

Delegate

Quand vous faites une action sur l'interface (clic sur un bouton, bouger un curseur, ...), vous voulez qu'un message soit transmis au Controleur. En quelque sorte le Controleur est le delegate de l'interface, c'est à dire que quand un message est transmis, c'est lui qui reçoit. Mais la notion de delegate est beaucoup plus générale me semble t'il. Ainsi le Application Delegate est le délégué de l'application. Quand l'application vient de finir de loader la fenêtre et qu'elle est prête, elle envoie le message - (void) applicationWillFinishLaunching: (NSNotification *) aNotification; à son delegate. Si aucun delegate n'a été spécifié, personne ne récupère ce message et il n'y a pas mort d'homme.

On spécifie qui est un delegate de qui dans Interface Builder, de la même manière qu'on définit les actions et les outlets.

Le mieux est de regarder en même temps sur un exemple (cf codes sources) pour suivre.

D'après ce que j'ai compris, l'architecture "3-tiers" signifie : une classe qui gère les données, une autre qui gère l'interface graphique, et une autre qui gère le passage entre les deux, les événements (souris, clic sur un bouton, ...). Ça c'est un petit peu la définition Wikipédia mais ça ne dit pas comment faire en pratique : qui est créée en premier ? la classe données ? la classe graphique ? l'autre ? Qu'est ce que c'est que ce "delegate" dont tout le monde parle ? Comment affecter une référence de Controleur à MaView si Controleur est créé après MaView ? ou l'inverse ?

Voilà comment je vois les choses, et comment je procède à chaque fois. Je ne pense pas que ça soit la meilleure solution, mais ça ne marche pas trop mal. J'utilise 4 classes : Application Delegate, MaView, Controleur et Cœur.

Application Delegate
Ceci est la classe "bougie d'allumage" en quelque sorte. C'est celle qui sera appelée en premier, et qui nous dit quand le logiciel a fini d'être loadé, et quand tout peut commencer.
MaView
Ceci est une classe optionnelle. Quand on a besoin de dessiner quelque chose on l'utilisera (jeu de serpent, ...), sinon on ne l'utilise pas (convertisseur franc-euro, ...)
Controleur
Il reçoit les événements (clic sur un bouton, ...), gère les objets sur l'interface ("afficher 'toto' dans la zone de texte", ...) et sert de zone de dialogue entre la vue et le système
Cœur
C'est le cœur du système. Dans le jeu du serpent, par exemple c'est là qu'on enregistre les serpents, le jeu, que "l'intelligence artificielle" décide de comment faire avancer les serpents ennemis, ...

Comme je vous le disais, ça a l'air propre et structuré, mais en pratique il y a des "conflits" avec ce modèle. Par exemple, MaView est la classe qui s'occupe de dessiner les serpents. Donc elle demande à Cœur de lui fournir la liste des cases à colorier (le corps des serpents). Mais alors si elle fait ça Controleur ne sert plus à rien alors qu'il est sensé être là pour faire le dialogue entre les deux. On pourrait bien sûr faire passer ce message via le Controleur mais c'est du code en plus pour rien, non ?

D'autre part, quand on clique quelque part sur la fenêtre et qu'on cherche à connaître les coordonnées de la souris à l'endroit du clic, est-ce un événement à gérer par le Controleur ou par MaView ? Bon, vous voyez qu'il y a pleins de petites questions, mais ça n'empêche pas de faire des logiciels qui tournent quand même.

Vous pouvez télécharger le projet minimal que je propose. Regardez bien les outlets (rappel : fenêtre des propriétés d'un objet) pour voir où les liens ont été mis.

Si vous pensez que le projet est un petit peu alambiqué pour un simple projet, vous avez raison. Mais il y a aussi une raison : Quid du controleur ou de MaView va être créé en premier quand le logiciel va être lancé ? Ils sont tous les deux instanciés automatiquement dès l'exécution du programme et on ne peut pas vraiment savoir ce qui va se passer. Il faut donc attendre que tout soit bien fait et créé, puis, quand l'Application envoit le message -(void) applicationWillFinishLaunching: (NSNotification *) aNotification; à son delegate, celui-ci sait que tout a été créé et il peut donc demander aux gens de se référencer les uns les autres. Il y a aussi un IBOutlet du AppDelegate vers le Controleur, et un IBOutlet du Controleur vers MaView pour aider !

Pour info, j'ai eu un bug difficile à détecter, qui disait qu'un pointeur était nul alors que pourtant MaView et Controleur semblaient tous deux en état de marche. C'est effectivement que l'un était créé avant l'autre.

Evenements souris et clavier

Dans une classe MaView héritant de NSView, mettez les fonctions suivantes (dans l'interface et dans l'implémentation) :

-(void) mouseDown:(NSEvent*) theEvent {
NSPoint pt = [theEvent locationInWindow];
pt.x -= [self frame].origin.x; pt.y -= [self frame].origin.y;
NSLog(@"mouseDown à x=%f y=%f", pt.x, pt.y);
}

-(void) mouseUp:(NSEvent*) theEvent {
}

-(void) mouseMoved:(NSEvent*) theEvent {
}

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

-(void) keyDown:(NSEvent *)theEvent {
// bas:125, haut:126, gauche:123, droite:124
NSLog(@"la touche numéro %d est appuyée", [theEvent keyCode]);
NSString *characters = [theEvent charactersIgnoringModifiers];
for (int i = 0; i < [characters length]; i++)
NSLog(@"le caractère %c est appuyé",[characters characterAtIndex:i]);
}

-(BOOL) acceptsFirstResponder {return YES;}
-(BOOL) becomeFirstResponder {return YES;}
-(BOOL) resignFirstResponder {return YES;}

Pour pouvoir avoir accès à mouseMoved, il faut de plus ajouter [appFenetre setAcceptsMouseMovedEvents:YES]; dans la fonction applicationWillFinishLaunching de l'AppDelegate. appFenetre est ici un IBOutlet sur la NSWindow* de l'application (c'est à dire la fenêtre contenant la view).

Facilités de Cocoa

Undo Manager

L'Undo Manager est l'objet Cocoa qui permet de gérer tout seul ce qui se passe quand on appuie sur Annuler ou Répéter (Redo). Le seul problème est qu'il n'y a pas d'Undo Manager pour toute l'application, il y en a un par fenêtre seulement (c'est ce que j'ai l'impression). Il faut donc que dans toutes les classes qui aient besoin d'appeler l'Undo Manager, il y ait une référence vers la fenêtre NSWindow de l'application.

Principe de fonctionnement : quand la classe va faire une action que l'on considère comme digne d'intérêt pour le Undo, elle spécifie à l'Undo Manager quelles fonctions appeler pour faire Undo et Redo sur cette action. L'Undo Manager place simplement ces appels de fonction sur des piles et les appelera au moment voulu.

Exemple : vous gérez une liste de personnes, avec une petite zone de texte et un bouton OK pour ajouter une personne à votre liste avec son nom. Au lieu de faire [liste addObject:[[Personne alloc] init:nom]];, vous allez faire [self ajouter:[[Personne alloc] init:nom]];.

-(void) ajouter:(id) qqchose {
[[fenetre undoManager] registerUndoWithTarget:self selector:@selector(retirer:) object:qqchose];
[liste addObject:qqchose];
}
-(void) retirer:(id) qqchose {
[[fenetre undoManager] registerUndoWithTarget:self selector:@selector(ajouter:) object:qqchose];
[liste removeLastObject];
}

Ici, fenetre est une NSWindow*.

Lecture / Sauvegarde de données

La sérialisation est le procédé qui consiste à sauvegarder les objets dans un fichier. Par exemple, vous avez un tableau avec des objets "Personne" (attributs : nom et age) et vous voulez sauvegarder vos données dans un fichier. Comment faire ? Vous pouvez soit ouvrir un fichier, écrire le nombre d'éléments dans le tableau, puis pour chaque élément écrire les attributs, mais c'est un petit peu moche.

Cocoa rend le procédé plus formel et plus simple. Un objet que vous souhaitez sérialisable doit conformer au protocole <NSCoding>, c'est à dire qu'il doit implémenter les fonctions suivantes : -(void) encodeWithCoder: (NSCoder *)coder et -(id) initWithCoder:(NSCoder *) coder.

Pour sauvegarder quelque chose, on a juste à dire "sauvegarde ceci". Et récursivement, toutes les "dépendances" de l'objet vont être sauvegardées.

Dans le cas de l'objet personne (int age, NSString* nom), on aurait par exemple :

-(void) encodeWithCoder: (NSCoder *)coder {
[coder encodeObject: [NSNumber numberWithInt:age] forKey:@"age"];
[coder encodeObject: nom forKey:@"nom"];
}

-(id) initWithCoder:(NSCoder *) coder {
if (self = [super init]) {
age = [[coder decodeObjectForKey:@"age"] intValue];
nom = [[coder decodeObjectForKey:@"nom"] retain];
}
return self;
}

Et dans Personne.h : class Personne : NSObject<NSCoding> {.... Si une super classe est déclarée comme conforme à NSCoding, alors les classes filles le sont automatiquement.

Sauvegarde

Pour ouvrir une boite de dialogue et sauvegarder un tableau contenant des objets 'Personne' :

NSSavePanel *sp = [NSSavePanel savePanel];
[sp setRequiredFileType:@"txt"];

int runResult = [sp runModalForDirectory:NSHomeDirectory() file:@""];
if (runResult == NSOKButton) {
NSString *fichier = [[sp filename] stringByExpandingTildeInPath];
NSMutableDictionary *rootObject = [NSMutableDictionary dictionary];
[rootObject setValue:tableau forKey:@"tableauPersonnes"];
[NSKeyedArchiver archiveRootObject:rootObject toFile:fichier];
}

Lecture

Pour ouvrir une boite de dialogue et ouvrir le fichier avec le tableau contenant des objets 'Personne' :

NSOpenPanel* openDlg = [NSOpenPanel openPanel];
NSArray *fileTypes = [NSArray arrayWithObject:@"txt"];
if ([openDlg runModalForDirectory:nil file:nil types:fileTypes] == NSOKButton ) {
NSString* fichier = [openDlg filename];
NSDictionary *rootObject = [NSKeyedUnarchiver unarchiveObjectWithFile:fichier];

tableauPersonnes = [rootObject valueForKey:@"listeFigures"];
}

Dessiner

Rien de plus simple, il vous suffit d'une view, ou plutôt d'une classe qui en dérive. Dans Interface Builder, déposez une view sur la fenêtre, allez dans ses propriétés, et changez la classe NSView en MaView, après bien sûr avoir créer un classe MaView dans votre projet ! Redéfinissez simplement la méthode drawRect dans l'interface (le .h) et dans l'implémentation (le .m).

Pour demander à une NSView de se redessiner, on lui envoie le message setNeedsDisplay:YES, et elle transmet tout ce qu'il faut à drawRect.

Dans MaView.h :

@interface MaView : NSView {
}

-(void) drawRect: (NSRect) bounds;
@end

Dans MaView.m :

#import "MaView.h"

@implementation MaView

- (void)drawRect:(NSRect)rect {
// dessiner un rectangle
[[NSColor blackColor] set];
NSRect cadre = NSMakeRect(0, 0, parametres.width, parametres.height);
[NSBezierPath strokeRect:cadre];

// dessiner une ligne
NSBezierPath* aPath = [NSBezierPath bezierPath];
[aPath moveToPoint:NSMakePoint(100, 0)];
[aPath lineToPoint:NSMakePoint(150, 200)];
[aPath stroke];

// dessiner une chaine de caractères dans une certaine couleur
NSDictionary* atts = [NSDictionary dictionaryWithObjectsAndKeys:[NSColor redColor], NSForegroundColorAttributeName, nil];
[@"toto" drawAtPoint:NSMakePoint(50, 50) withAttributes:atts];

// dessiner un oval
NSRect r = NSMakeRect(10, 10, 40, 70);
aPath = [NSBezierPath bezierPathWithOvalInRect:r];
[aPath stroke];
}

Commandes en vrac

Class aa = [A class]; A* toto = [[aa alloc] init];
Class est un type. On peut avoir une variable de type class, ce qui peut être pratique.
[toto isMemberOfClass:[A class]]
pour savoir si l'objet toto est de type A ou pas
[toto isKindOfClass:[A class]]
pour savoir si l'objet toto est de type B où B est une classe héritant de manière directe ou non de A.

Jouer un son

Pour ce faire, inclure le fichier son dans le bundle (cliquer sur le petit dossier Resources et faire AddFile). Il ne suffit pas d'avoir le fichier son dans le dossier de l'application. Dans le code ci-dessous, le fichier est "supermusique.wav"

NSString* monFichSon = [bundle pathForResource:@"supermusique" ofType:@"wav"];
NSSound* monSon= [[NSSound alloc] initWithContentsOfFile:monFichSon byReference:YES];
[monSon play];

Afficher une image

Pour ce faire, inclure l'image dans le bundle (cliquer sur le petit dossier Resources et faire AddFile). Il ne suffit pas d'avoir l'image dans le dossier de l'application. Dans le code ci-dessous, le fichier est "monimage.png"

NSURL *imURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"monimage" ofType:@"png"] isDirectory:NO];
NSImage* monImage = [[NSImage alloc] initWithContentsOfURL:imURL];
[bouton setImage:monImage];

Préférences

Il existe un objet spécial sur toutes les applications : les préférences. Apple gère le fichier pour nous.

Quand on écrit dans le fichier, il faut faire synchronize après. Mon expérience des préférences est que ça ne fonctionne pas toujours bien. Quand on écrit dedans et qu'on ferme l'application et qu'on l'ouvre après, les préférences ont bien été sauvegardées, mais si on ne ferme pas l'application avant et qu'on cherche dans un autre coin du code à savoir ce qu'il y a dans tel préférence, il se peut très bien que le changement n'est pas été fait...

NSUserDefaults *preferences = [[NSUserDefaults standardUserDefaults] retain];
// lire des préférences
int a = [preferences integerForKey: @"Niveau"];

// écrire dans les préférences
[preferences setInteger:a+1 forKey:@"Niveau"];
[preferences synchronize];

Pour information, il y a aussi un autre truc, ça s'appelle les "bindings". C'est dans les propriétés d'un objet dans Interface Builder. Si par exemple il y a une case à cocher quelque part, l'application va sauvegarder toute seule l'état de cette case (cochée ou non), pour ne pas qu'on ait à le faire à chaque fois. Je n'ai jamais vraiment essayé ça, mais il faut savoir que ça existe.

Imprimer

Petit bout de code pour imprimer une NSView* window.

NSPrintInfo *printInfo = [NSPrintInfo sharedPrintInfo];
[printInfo setHorizontalPagination: NSFitPagination];
[printInfo setVerticalPagination: NSFitPagination];
[printInfo setVerticallyCentered:NO];
NSLog(@"Printing with parameters %@", [printInfo dictionary]);
NSPrintOperation *op = [NSPrintOperation printOperationWithView:window printInfo:printInfo];
[op setShowPanels:YES];
[op runOperation];

OpenGL

Si vous décidez d'avoir une OpenGL View au lieu d'avoir une NSView, c'est comme vous décidez. Au lieu d'avoir une MaView : NSView, faites MaView : NSOpenGLView. Incluez aussi le framework OpenGL dans le petit dossier Frameworks de votre projet XCode.

#include <OpenGL/gl.h>

-(void) drawRect:(NSRect) bounds {
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);

glColor3f(.8, .6, .4);

glBegin(GL_POLYGON);
{
glVertex2f( x, y); // haut à gauche
glVertex2f( x + widt, y); // haut à droite
glVertex2f( x + widt, y - heigh); // bas à droite
glVertex2f( x, y - heigh); // bas à gauche
}
glEnd();

glFlush();
}

iPhone

Il n'y a que très peu de différences entre Cocoa et iPhone. En fait j'en ai surtout noté pour ce qui est du dessin. Au lieu de faire des choses comme NS.... on fait CG... (pour les structures). Par exemple :

-(void) drawRect: (CGRect) bounds {
CGContextRef myContext = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor (myContext, [[NSColor redColor] CGColor]);
CGContextFillRect (myContext, CGRectMake (x, y, widt, heigh ));
}

Sinon il n'y a pas de Garbage Collector (à l'heure où je le dis) sur iPhone, donc vous devez gérer la mémoire vous même !

À lire aussi :
  • iPhone Program snippets iPhone/Objective-C
  • Couleurs Fonctionnement des couleurs sur un ordinateur, et transformation HLS<->RGB
  • Fichiers Wav Format, spécifications et code source C++ générant un .wav
  • Mes codes open source Des dizaines de sources et snippets, pour apprendre ou se perfectionner
  • Fils RSS qu'est ce qu'un fil RSS et comment en créer ?
  • Automator et Applescript Impimer plusieurs liens URL / pages internet en PDF
    Print many URL / webpages in PDF
  • le Sénat il y en a qui ont la belle vie !
  • Hokkaido Encore un bon petit week end découverte, plus loin cette fois !
  • Jongler Je sais enfin jongler !!
  • Night Call Du sombre dans les salles sombres
Laissez un commentaire !

Pas besoin de vous connecter, commencez à taper votre nom et une case "invité" apparaîtra.