Archivi tag: Delegate

Kingdom:Undead Control Handlers

Nel disegno del codice, mi sono accorto di una cosa che a tendere rendeva illeggibile e poco gestibile il codice, la gestione degli eventi mouse, touch e keyboard da parte dei CCLayer di cocos2d.

Dovendo mostrare diversi CCNode contenenti UI realizzate con cocos, come ad esempio la scheda del giocatore, piuttosto che un menu di scelta, una popup modale magari realizzata con texture fatte adhoc, la gestione degli eventi rimaneva tutta comunque nell’implementazione del cclayer contenente tutta la parte principale del gioco.

Così ho deciso di estendere il cclayer, aggiungendo la possibilità di fornire gli eventi a dei delegati. Questo mi permette di “responsabilizzare” il codice al meglio, lasciando ad esempio la gestione del click su un certo pulsante solo nel ccnode che lo contiene.

Questo e’ un esempio di come estendere il CCLayer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//
// CCLayerControlServer.h
// KingdomUndead
//
// Created by Andrea Magini on 04/04/12.
// Copyright (c) 2012 metalide. All rights reserved.
//

#import
#import "cocos2d.h"
#import "KeyboardControlHandler.h"
#import "MouseControlHandler.h"

@interface CCLayerControlServer : CCLayer{
@private

idkeyboardDelegate;
id mouseDelegate;
}
@property (nonatomic,retain)id keyboardDelegate;
@property (nonatomic,retain)id mouseDelegate;

@end

E questa la sua implementazione:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//
// CCLayerControlServer.m
// KingdomUndead
//
// Created by Andrea Magini on 04/04/12.
// Copyright (c) 2012 metalide. All rights reserved.
//

#import "CCLayerControlServer.h"

@implementation CCLayerControlServer
@synthesize keyboardDelegate;
@synthesize mouseDelegate;
-(id) init
{
if( (self=[super init])) {
self.isMouseEnabled = YES;
self.isKeyboardEnabled = YES;
}
return self;
}

-(BOOL) ccKeyUp:(NSEvent *)event
{

[keyboardDelegate handleKeyUp:event];
return NO;
}

-(BOOL) ccMouseUp:(NSEvent *)event{
[mouseDelegate handleMouseUp:event];
return YES;
}

-(BOOL) ccMouseDragged:(NSEvent *)event{
[mouseDelegate handleMouseDragged:event];
return YES;
}

-(void)dealloc{
[keyboardDelegate release];
[mouseDelegate release];
[super dealloc];
}
@end

In questo modo tutti gli eventi intercettati dal layer vengono passati di volta in volta ai delegati registrati.
In ogni momento posso ad esempio deregistrare il layer come delegato per gli eventi e registrare un particolare CCNODE che gestisce una popup che si e’ appena mostrata a video.

I delegati devono avere un particolare Protocol per rispondere agli eventi mouse, o tastiera (o touch su iOS)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//
//  KeyboardControlHandler.h
//  KingdomUndead
//
//  Created by Andrea Magini on 04/04/12.
//  Copyright (c) 2012 metalide. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <Carbon/Carbon.h>

//per gestire il controllo sui vari ccnode e layers in modo da abilitare e disabilitare il controller

@protocol KeyboardControlHandler <NSObject>
@required
-(void) handleKeyUp:(NSEvent *)event;
@end

Questo permette di snellire il codice del CCLayer evitando di mettere tonnellate di if nei vari mousebuttonup, down, keyup etc e andando invece a gestire gli eventi
in maniera opportuna nei vari ccnode contenuti nella scena principale. Un esempio pratico è quello per la gestione degli eventi sull’HUD , lasciandoli gestire direttamente al CCNODE in questione, separandolo dagli eventi di gioco.

Come registrare un delegato:

1
2
3
@interface HudNode : CCNode<KeyboardControlHandler>{

}

Nel codice del CCLayer principale, durante l’init registro il CCNode dell’hud, e poi lo imposto come delegato per la gestione degli eventi tastiera

1
2
   [self addChild:hudNode];
   [self setKeyboardDelegate:hudNode];

e poi nel CCNode che implementa l’hud, implemento il metodo del Protocol KeyboardControlHandler

1
2
3
4
5
6
7
-(void) handleKeyUp:(NSEvent *)event{
    CCLOG(@"key up: %@", [event characters] );
    UInt16 keyCode = [event keyCode];
    if (keyCode == kVK_Space) {
       //fai qualcosa :)
    }
}

Spero sia chiaro 🙂

NSXmlParser il default XmlParser di iOS

Per chi lavora da molto con la programmazione, sia Java che .Net, avere a disposizione un Dom per la lettura dei files xml tramite un documento a grafo e magari espressioni xpath, è oramai legge.

Eppure per lavorare su IPhone tramite iOS e in generale su Mac OSX, di default troviamo solo ed esclusivamente un SaxParser, l’NSXmlParser..

Superato l’impatto delle Sax (da sempre ostiche ma sicuramente molto performanti), facciamo un velocissimo esempio di come usarle in una nostra classe, per leggere velocemente dei dati da un file xml.

Innanzitutto aggiungiamo l’interfaccia  NSXMLParserDelegate alla classe che riceverà gli eventi Sax, perche le Sax lavorano completamente ad eventi. Quando verrà lanciato il parser, scatteranno eventi ogni volta che si verificherà una condizione (aperto nodo, presenza testo, chiuso nodo, etc etc etc )

1
@interface MiaClasse : NSObject <NSXMLParserDelegate>

 

in questa classe (che può essere una qualsiasi classe del nostro progetto, dichiariamo l’xml parser nell’header , l’elemento che conterrà l’array di dati che andiamo ad estrarre, i puntatori per i singoli valori (on the fly come variabili secche, ma in genere meglio definire un oggetto Item che esponga tutti gli attributi), il puntatore all’elemento xml corrente che il parser sta analizzando:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@interface MiaClasse : NSObject <NSXMLParserDelegate>

//parser

NSXMLParser *xmlParser;

//array che conterrà i dati

NSMutableArray *elencoItems;

//variabile temporanea per ogni elemento

NSMutableDictionary *item;// nome del nodo corrente che il sax parser sta analizzando

NSString *currentElement;

//elementi che leggeremo e poi memorizzeremo

NSMutableString *nome, *cognome, *indirizzo;

ora che abbiamo definito l’header, passiamo all’implementazione.

Inizializziamo il parser e l’array di dati nel metodo in cui lo andiamo ad usare, nel costruttore o dove vogliamo.

In questo caso andiamo a leggere il file xml da risorsa locale, ma possiamo accedere tranquillamente ad un file remoto .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)applicationDidFinishLaunching:(UIApplication *)application {

elencoItems= [[NSMutableArray alloc] init];

xmlParser = [[NSXMLParser alloc] initWithData:[NSData dataWithContentsOfFile:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"test.xml"]  ]];

//definiamo il delegato che riceverà gli eventi (in questo caso la classe stessa

[xmlParser setDelegate:self];

//saltiamo i problemi di namespace

[xmlParser setShouldProcessNamespaces:NO];

[xmlParser setShouldReportNamespacePrefixes:NO];

[xmlParser setShouldResolveExternalEntities:NO];

// avviamo il parsing XML

[xmlParser parse];

}

 

ora definiamo i metodi essenziali per soddisfare l’interfaccia NSXMLParserDelegate:

didStartElement , che viene eseguito ad ogni inizio di elemento xml trovato

didEndElement, che viene eseguito quando viene analizzata la chiusura di un tag xml

foundCharacters, che viene eseguito quando si analizza il contenuto di tipo “text” di un elemento

parserDidEndDocument, che scatta quando si raggiunge la fine del documento xml.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{

currentElement = [elementName copy];

//qui mettiamo tutte le regole di match del nostro xml, ad esempio cosa fare quando

// si incontra un nodo denominato "persona"

if ([elementName isEqualToString:@"persona"]) {

// inizializza tutti gli elementi che conterranno i dati. memorizzeremo ogni item come tabella per praticità

item = [[NSMutableDictionary alloc] init];

nome = [[NSMutableString alloc] init];

cognome = [[NSMutableString alloc] init];

indirizzo = [[NSMutableString alloc] init];

}

}

 

Nel metodo didEndElement andremo fisicamente a prendere i dati letti e a memorizzarli in una struttura (il nostro array)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{

if ([elementName isEqualToString:@"persona"]) {

[item setObject:nome forKey:@"nome"];

[item setObject:cognome forKey:@"cognome"];

[item setObject:indirizzo forKey:@"indirizzo"];

[elencoItems addObject:[item copy]];

}

}

 

Ed ora il metodo che fisicamente legge i dati dai nodi:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {

if ([currentElement isEqualToString:@"nome"]){

[nome appendString:string];

} else if ([currentElement isEqualToString:@"cognome"]) {

[cognome appendString:string];

} else if ([currentElement isEqualToString:@"indirizzo"]) {

[indirizzo appendString:string];

} }

 

Infine se a termine del traversing del grafo xml dobbiamo effettuare delle operazioni, come prendere il risultato e passarlo ad altri oggetti, utilizziamo il parserDidEndDocument:

1
2
3
4
5
6
7
- (void)parserDidEndDocument:(NSXMLParser *)parser {

[mioOggetti setData:elencoItems];

[elencoItems release];

}

In linea di massima e’ molto semplice utilizzare XmlParser.

Come tutti i framework e metodologie su iOS si fa un grande uso dei delegati.

Spero di esservi stato di aiuto.