AppDot 5 - part1 - Jaime

Bé, com sempre, el que m'interessa es aprendre a fer programes per l'iPhone, i ja que encara que el codi es molt similar, hi han petites diferencies, es per això que m'hi llenço de cap i faig tots els exercicis adaptant-los...

He escollit la versió 3 perquè he pensat que era millor per fer l'excercici amb els mètodes que he pensat, que es resumeixen així:
- Moure i canviar els "dots" individualment.
- Aplicar els canvis al últim dot que ha sigut seleccionat.

El que he fet es posar dos UIViews en la View principal en comptes de utilitzar dos Window amb un NSView cadascuna. L'altre diferencia es que en comptes de posar un Colorwell, he fet quatre botons, cada un d'ells canvia el color de un dot per vermell, verd, blanc o negre. Però la funcionalitat en general es la mateixa i l'aproximació a com fer l'exercici també. Així dons, passo a comentar el que he fet i el que he aconseguit... i el que no.

En aquest exercici, la primera part ens diu que hem de tenir dos "dots", així que com ja tenim un, posem un de nou, i canviem l'arxiu "DotView.h" així:

@interface DotView : UIView {
IBOutlet Dot *dot;
IBOutlet Dot *dot2;
NSString *selectedDot;
IBOutlet UISlider *slider;
}

Aquí definim el nou dot com a "dot2" i per seguir l'exemple anterior, com a IBOutlet. Això vol dir que hem de fer una nova instància de la classe Dot al IB, i fer-hi les connexions adequades. Aquesta part no la acabo d'entendre bé, però ho he intentat fer amb només una instància i s'esborra un dels dots... Continuem.
He posat un NSString perquè així podem sapiguer quin dot estem utilitzant. Ja veurem com mes endavant. Volia utilitzar una referència a la instància de Dot, però no ho he aconseguit...
També he posat un nou outlet, un UISlider, que es com el NSScroll, i que serveix per canviar la llargada del radi. L'he posat aquí perquè he pensat que estaria bé mostrar la llargada del radi del dot que escollim.

Bé, abans de continuar, he tingut que fer un petit retoc al dot. La pantalla de l'iphone es petita, per això he fet el radius per defecte mes petit, uns 20 pixels.

Anem al "DotView.m".

- (void) awakeFromNib{
// visualizar correctement el radius al slider
[slider setValue:dot.radius];

// reposicionar
[dot setCenter:CGPointMake(100,100)];
[dot2 setCenter:CGPointMake(200,100)];
[dot2 setColor:[UIColor greenColor]];
// observers
[dot addObserver:self forKeyPath:@"center" options:0 context:NULL];
[dot addObserver:self forKeyPath:@"color" options:0 context:NULL];
[dot addObserver:self forKeyPath:@"radius" options:0 context:NULL];
[dot2 addObserver:self forKeyPath:@"center" options:0 context:NULL];
[dot2 addObserver:self forKeyPath:@"color" options:0 context:NULL];
[dot2 addObserver:self forKeyPath:@"radius" options:0 context:NULL];
}

Aquí el primer canvi es al awakeFromNib i es fer que el slider mostri el radi del "dot" que en aquest moments es el mateix que el del "dot2". Després he canviat de posició els dots i he canviat de color el segon a verd, però poder diferenciar-los millor. Finalment, he posat els observers al "dot2" perquè es vegin els canvis en el segon UIView.

Per re-dibuixar la pantalla, el que he fet es posar la definició del dot2 de forma exacte a com s'havia fet amb el primer dot:

- (void)drawRect:(CGRect)rect {
// Cambiar el color de la vista a blau
CGRect bounds = [self bounds];
[[UIColor blueColor] set];
UIRectFill(bounds);

//Dot 1
CGRect dotRect1= CGRectMake(dot.center.x - dot.radius,
dot.center.y - dot.radius,
2*dot.radius,
2*dot.radius);
[[dot color] set];
UIRectFill(dotRect1);

//Dot 2
CGRect dotRect2= CGRectMake(dot2.center.x - dot2.radius,
dot2.center.y - dot2.radius,
2*dot2.radius,
2*dot2.radius);
[[dot2 color] set];
UIRectFill(dotRect2);
}

Ara anem a la part mes interessant, però a la vegada senzilla, i es detectar el moviment
per fer això, hem de utilitzar dos mètodes del UITouch.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// Veure si cap dels 2 dots ha sigut triat
// recordar quin
CGPoint touch=[[touches anyObject] locationInView:self];
if(touch.x>dot.center.x-dot.radius && touch.x<dot.center.x+dot.radius && touch.y>dot.center.y-dot.radius && touch.y<dot.center.y+dot.radius)
{
[dot setCenter:touch];
[selectedDot release];
selectedDot=@"dot";
[slider setValue:dot.radius animated:YES];
}else if(touch.x>dot2.center.x-dot2.radius && touch.x<dot2.center.x+dot2.radius && touch.y>dot2.center.y-dot2.radius && touch.y<dot2.center.y+dot2.radius)
{
[dot2 setCenter:touch];
[selectedDot release];
selectedDot=@"dot2";
[slider setValue:dot2.radius animated:YES];
};
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesBegan:touches withEvent:event];
}

Trobem un punt, veiem si es dins de un o l'altre dot i aleshores posicionem el dot, el recordem com l'ultim dot en se mogut a selectedDot i corregim el slider per que mostri el radi d'aquest dot. La part que diu animated:YES el que fa es que el slider faci una petita animació fins la seva posició correcte.

D'aquesta manera, ara podem corregir el mètode changeRadius d'aquesta manera:

- (IBAction)changeRadius:(id)sender {
if([selectedDot isEqualToString:@"dot"])
{
[dot setRadius:[[sender valueForKey:@"value"] floatValue]];
}else if([selectedDot isEqualToString:@"dot2"])
{
[dot2 setRadius:[[sender valueForKey:@"value"] floatValue]];
};
[self setNeedsDisplay];
}

i el color de forma similar:
- (IBAction) setSquareWhite:(id)sender{
if([selectedDot isEqualToString:@"dot"])
{
[dot setColor:[UIColor whiteColor]];
}else if([selectedDot isEqualToString:@"dot2"])
{
[dot2 setColor:[UIColor whiteColor]];
};
[self setNeedsDisplay];
}

(aquest mètode correspon a l'acció d'un botó, ho he tingut que fer així per allò de que no hi ha Colorwell al iPhone).

Dons aquesta es la meva solució que funcion QUASI perfectament.

El problema es quan es canvia de radi un dot a un view i desprès canvies l'altre dot però al altre view... per alguna raó es confon i acaben els dos dots amb el mateix radi...

Bé, espero que us hagi interessant

Jaime  sep  01 juliol 2008 17:41

<a href="http://imageshack.us"><img src="http://img60.imageshack.us/img60/5762/iphoneol5.jpg" border="0" alt="Image Hosted by ImageShack.us"/></a><br/><a href="http://g.imageshack.us/g.php?h=60&i=iphoneol5.jpg"><img src="http://img60.imageshack.us/img60/5762/iphoneol5.174c2d076d.jpg" border="0"></a>

Jaime  sep  01 juliol 2008 17:51

imatge

Jaime  sep  01 juliol 2008 17:52

M'he oblidat de dir que en l'iPhone el sistema de coordenades esta invertit per defecte, això vol dir que l'origen es a la part superior-esquerra, com a una plana web.

Jaime  sep  01 juliol 2008 17:55

Prou bé !! :)
Encara que jo no ho hagués fet així. ;)
Per cert, com és que fas els punts quadrats?
I com t'ho faries per permetre afegir i eliminar nous punts? :D

Viam si algú més s'hi anima. :(

Em sembla que miraré de resoldre aquests exercicis amb l'iPhone, així podré practicar. Però deixaré unes setmanes més, per que hi hagi més temps per fer-ho.

Xin  sep  01 juliol 2008 21:36

Ja he fet les meves primeres proves. :-D No està malament.

imatge

Xin  sep  01 juliol 2008 22:36

En faré unes quantes classes. Així introduïm el desenvoluoament amb l'iPhone.

Un pregunta a qui tingui un iPhone o iPod Touch. Es pot prémer a més de 2 llocs a l'hora? És que el simulador només emula el moviment de dos dits de forma simètrica. :(

Xin  sep  01 juliol 2008 22:39

Una prova més, i sembla ser que l'emulador no emula el multi-touch. :(

imatge

Xin  sep  01 juliol 2008 23:02

Si, no emula el multi touch, només el pinch i la inversa del pinch... i la raó per que he fet quadrats es perque no hi ha NSBezierPath, o si? Quan ho vaig intentar el primer cop, no sé amb quina beta, no em deixava...

Jaime  sep  02 juliol 2008 00:54

Si mires la documentació de la NSBezierPAth, veuràs que només és una interfície de les funcions de Quartz2D. I si a l'iPhone no hi és, cal anar a l'arrel i trobar les funcions bàsiques.

Jo he fet servir aquesta funció: CGContextFillEllipseInRect I també cal carregar la framework CoreGrafics. (Suposo que carregar la llibreria Quartz2D també serviria). Les llibreries de l'iPhone jo les agafo en aquest cas de:

/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS2.0.sdk/System/Library/Frameworks/CoreGraphics.framework/

Xin  sep  02 juliol 2008 07:47

Grácies, ara m'ho miro! Per cert, els iPhones/iPodTouch detecten més de dos punts a l'hora, pero no recordo cuants

Jaime  sep  02 juliol 2008 08:25

Va bé saber-ho. Així ho puc tenir en compte a l'hora d'inter-actuar amb l'usuari.

Xin  sep  02 juliol 2008 13:53

Bé, he fet unes quantes millores que voldria comentar.

Primer he substituït els quadrats per cercles, per això he utilitzat la funció que m'has dit, però la veritat es que encara que he llegit la documentació, m'ha costat una mica. El resultat:

- (void)drawRect:(CGRect)rect {

// Definir el contexto, clear para mejor animación?
CGContextRef ctx = UIGraphicsGetCurrentContext();

// Cambiar el color de la vista a blau
CGRect bounds = [self bounds];
[[UIColor blueColor] set];
UIRectFill(bounds);

//Dot 1
CGRect dotRect1= CGRectMake(dot.center.x - dot.radius,
dot.center.y - dot.radius,
2*dot.radius,
2*dot.radius);
[[dot color] set];
//CGContextSetRGBFillColor(ctx, 0, 255, 0, 1);
CGContextFillEllipseInRect(ctx, dotRect1);

//Dot 2
CGRect dotRect2= CGRectMake(dot2.center.x - dot2.radius,
dot2.center.y - dot2.radius,
2*dot2.radius,
2*dot2.radius);
[[dot2 color] set];
//CGContextSetRGBFillColor(ctx, 0, 255, 0, 1);
CGContextFillEllipseInRect(ctx, dotRect2);

}

Ha funcionat bé, però això ha creat un nou problema, ja que el codi que he utilitzat detecte un quadrat, i el cercle, com es obvi, no arriba fins els cantons. Sé que es voler ser massa precís i que si utilitzem un dit, es ben difícil trobar aquest error, però ja que hi som, ho he canviat d'aquesta manera:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// Fa click en dot ?
CGPoint touch=[[touches anyObject] locationInView:self];

double distanceA=touch.x-dot.center.x;
double distanceB=touch.y-dot.center.y;
if(dot.radius> sqrt( (distanceA*distanceA)+(distanceB*distanceB) ))
{
[selectedDot release];
selectedDot=@"dot";
[dot setCenter:touch];
[slider setValue:dot.radius animated:YES];
}

// Fa click en dot2 ?
distanceA=touch.x-dot2.center.x;
distanceB=touch.y-dot2.center.y;
if(dot2.radius> sqrt( (distanceA*distanceA)+(distanceB*distanceB) ))
{
[selectedDot release];
selectedDot=@"dot2";
[dot2 setCenter:touch];
[slider setValue:dot2.radius animated:YES];
}
}

Ara l'ultim que vull intentar es en comptes d'utilitzar un NSString per sapiguer quin ha sigut l'ultim dot pressionat, vull utilitzar una referencia del tipus Dot.

Ho he intentat fer d'aquesta manera i no sé perquè, es penja:

if(dot2.radius> sqrt( (distanceA*distanceA)+(distanceB*distanceB) ))
{
/*[selectedDot release];
selectedDot=@"dot2";*/
[sDot release];
sDot=dot2;
[dot2 setCenter:touch];
[slider setValue:dot2.radius animated:YES];
}

En realitat, durant una estona a funcionat bé i ara no... m'ho miraré una mica més.... :(
L'utilitat es per canviar els altres métodes, per exemple changeRadius seria:
- (IBAction)changeRadius:(id)sender {
[sDot setRadius:[[sender valueForKey:@"value"] floatValue]];
[self setNeedsDisplay];
}

Jaime  sep  02 juliol 2008 23:55

Hm... es penja al moure els cercles, no al canviar el radi o el color.... he fet alguna cosa malament?

Jaime  sep  02 juliol 2008 23:59

Hm... si no faig el release, si que funciona... Bé, la gestió de memoria es una cosa que encara no entenc bé... Al menys he pogut canviar també els canvis de color:

- (IBAction) setSquareWhite:(id)sender{
[sDot setColor:[UIColor whiteColor]];
[self setNeedsDisplay];
}

Jaime  sep  03 juliol 2008 00:13

La funció per saber si ets dins del cercle és correcte. De totes formes jo canviaria el codi per a desar la distància des del touch premut i cadascun dels punts. Així després pots seleccionar el punt més proper, i no executar la comparació per tots dos.

Veig que fas un "release" de l'NSString "selectedDot". Cal dir que si aquesta variable només conté cadenes estàtiques no serveix de res fer-ho. Jo et recomanaria crear-te els mètodes d'accés i utilitzar-los. I que el "setSelectedDot" reservi i alliberi la memòria. T'estalviaràs línies de codi i tindràs un codi més estable.

La segona forma que utilitzes establint el punt per referència, no ho fas correctament. Ja que alliberes el "sDot", però no el retens enlloc.

Has de recordar el que vaig explicar al tema de la gestió de memòria. Tens 3 opcions: copiar, retenir o assignar. Si copies o retens, hauràs d'alliberar-lo després. Si assignes, no has d'alliberar-lo, simplement canviar-li la referpencia. En el teu cas, jo utilitzaria l'assignació, ja que saps segur que els punts no desapareixeran (i tenir problemes de tenir una referència trencada), i així t'estalvies de retenir i alliberar.

Veig que ja has trobat el problema del "release". Pensa que quan ho feies, eliminaves l'objecte que havia creat l'Interface Builder, i per això l'aplicació petava. :)

Xin  sep  03 juliol 2008 09:53

Una altra recomanació, és que les funcions per canviar de color només en deixis una i assignis el "tag" de cada botó amb un número "0, 1, 2, 3". Més o menys així (comprova-ho perquè ho he escrit sense provar-ho.

- (IBAction) setDotColor:(id)sender {
NSArray *colors = [NSArray arrayWithObjects:
[UIColor redColor],
[UIColor greenColor],
[UIColor whiteColor],
[UIColor blackColor],
nil];
[sDot setColor:[colors objectAtIndex:[sender tag]]];
[self setNeedsDisplay];
}

Això és la millor opció en botons que bàsicament fan el mateix, només amb un canvi d'un paràmetre.

Xin  sep  03 juliol 2008 10:07

Ja he fet servir les teves correccions i tot molt bé, no sabia lo del "tag"!

A sobre la memòria, m'acabo de adonar que encara no he vist el vídeo! Quan l'hagi vist, intentaré acabar de corregir el que queda, a més em servira per la propera versió que tinc planejada per l'AppDot 5 :)

Per cert, el teu codi està bé, l'únic es que m'he adonat que he deixat el self setNeedsDisplay, cosa que no calia perquè tinc posats els observers...

Jaime  sep  03 juliol 2008 21:01

Tens raó, el "setNeedsDisplay" ja no cal.
Espero amb ànsia la teva nova versió. :)

Jo ja he fet una classe de programació per l'iPhone, explico com adaptar l'AppDot a l'iPhone. Més endavant resoldré l'exercici directament amb l'iPhone. Però abans vull saber com funcionen les finestres a l'iPhone, per poder fer una finestra per canviar el color.

Xin  sep  03 juliol 2008 21:27

Selecciona'l abans d'enviar el commentari