Objective C: Hide keyboard on touch outside

Problem
Das Keyboard für die Texteingabe soll bei einem Touch-Event außerhalb eines Textfeldes automatisch verschwinden, egal welches Textfeld gerade den Eingabefokus besitzt. Um das Keyboard auszublenden wird das aktive Textfeld benötigt, der sogenannte “firstResponder”.

[myActiveTextField resignFirstResponder];

In einer größeren Anwendung können eine ganze Reihe von Textfeldern existieren die in verschiedenen Views liegen. Somit stehen wir vor dem Problem, wer ist der aktuelle firstResponder und wie kommen wir an diesen heran?

Der erste Ansatz zur Lösung des Problems war, dass jeder View, der Textfelder enthält, selbst für das ausblenden des Keyboards sorgt. Diese Lösung stellte sich aber schnell als recht unflexible heraus, da der Code zum Ausblenden des Keyboards in jedem dieser Views hinterlegt werden muss. Erschwerend kommt hinzu, dass diese Views keine Touch-Events außerhalb ihres Frames empfangen. Wir benötigen also einen View (Hauptview-Controller), der auf alle Touch-Events innerhalb der Anwendung hört.

Die nächste Idee beinhaltete eine zentrale Verwaltungsstelle für alle Textfelder. Der Touch-Event würde dann vom Hauptview-Controller ausgewertet werden. Dieser prüft jedes registrierte Textfeld, ob dies unter der Position des Touchs liegt. Somit hätten wir zumindest einen Teil des Codes an einer zentralen Stelle, aber müssten jedesmal die Textfelder der aktuellen Subviews an- und abmelden. Dies wiederum bedeutet einen Mehraufwand für den Programmierer. Somit war diese Lösung auch nicht akzeptabel.

Glücklicherweise gibt es die Eigenschaft “isFirstResponder” der Klasse UIView.
Dies brachte mich auf die Idee eine Methode im Hauptview-Controller zu implementieren, welche rekursiv über alle Subviews läuft auf der Suche nach dem firstResponder. Der komplette Code, der sich um das Ausblenden des Keyboards kümmert, kann nun direkt im Hauptview-Controller hinterlegt werden.

Lösung
Wir benötigen eine Benachrichtigung wann das Keyboard angezeigt und ausgeblendet wird. Zusätzlich wollen wir über Touchs informiert werden.

- (void)viewDidLoad
{
    [super viewDidLoad];

    // add listener for keyboard events
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWasShown:) name:UIKeyboardDidShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillBeHidden:) name:UIKeyboardWillHideNotification object:nil];

    // register tap listener to detect taps
    _hideKeyboardTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action: @selector(onTapGestureDetected:)];
    _hideKeyboardTapRecognizer.enabled = FALSE;
    _hideKeyboardTapRecognizer.cancelsTouchesInView = NO; // this prevents the gesture recognizers to 'block' touches
    [self.view addGestureRecognizer:_hideKeyboardTapRecognizer];
}

- (void)viewDidUnload
{
    [super viewDidUnload];

    // remove the keyboard listener
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];

    // remove tap listener
    [self.view removeGestureRecognizer:_hideKeyboardTapRecognizer];
    [_hideKeyboardTapRecognizer release];
    _hideKeyboardTapRecognizer = nil;
}

Der Touch-Listener ist nur aktiv wenn das Keyboard eingeblendet ist, ansonsten benötigen wir keine Benachrichtigung.

- (void)keyboardWasShown:(NSNotification*)aNotification{
    // enable the tap listener
    _hideKeyboardTapRecognizer.enabled = TRUE;
}

- (void)keyboardWillBeHidden:(NSNotification*)aNotification{
    // disable the tap listener
    _hideKeyboardTapRecognizer.enabled = FALSE;
}

Sobald wir ein Touch-Event erhalten, prüfen wir ob dieser außerhalb eines Textfeldes stattfand und blenden in diesem Fall das Keyboard aus.

- (void)onTapGestureDetected:(UITapGestureRecognizer *)recognizer {
    // get the location of the tap
    CGPoint location = [_hideKeyboardTapRecognizer locationInView:self.view];

    // get the view which is under the location
    UIView *hittedView = [self.view hitTest:location withEvent:nil];

    // check if the view contains the first responder
    UIView *firstResponder = [self findFirstResponder:hittedView];

    if(firstResponder){
        location = [_hideKeyboardTapRecognizer locationInView:firstResponder];
        // check if the tap was inside the first responder
        if(CGRectContainsPoint(firstResponder.bounds, location)){
            return;
        }
    }else{
        // if not, we start the search from the main view
        firstResponder = [self findFirstResponder:self.view];
    }

    // hide the keyboard
    [firstResponder resignFirstResponder];
}

- (UIView *)findFirstResponder:(UIView*)startView{
    if ([startView isFirstResponder]){
        return startView;
    }
    UIView * firstResponder;
    for(UIView * subView in startView.subviews) {
        firstResponder = [self findFirstResponder:subView];
        if (firstResponder != nil){
            return firstResponder;
        }
    }

    return nil;
}

Ich habe zum Testen ein einfaches Beispiel als Xcode-Projekt aufgesetzt, welches den Code in Aktion zeigt. Dies funktioniert auch mit verschachtelten Views. Wichtig ist, dass der Code im Hauptview-Controller der Anwendung hinterlegt ist. Ansonsten kann der firstResponder nicht in Views gefunden werden, welche außerhalb des Hauptviews liegen.

Posted in Objective-C, iPhone | Tagged , , , , , , , | Leave a comment

Xcode: Frag den Debugger

Leider bietet die Ausgabe des Debuggers nicht immer Einsicht in alle Eigenschaften einer Instanz, so das sich viele Programmierer mit NSLog-Aufrufen behelfen, um zum Beispiel die Anzahl der Elemente in einem Array zu erhalten. Doch diese Information kann auch zur Laufzeit mit zusätzlichen Kommandos über die Konsole des Debuggers erfragt werden.

Folgendes Beispiel, welches wir Stück für Stück mit Hilfe des Debuggers genauer analysieren, erstellt ein Array mit zwei ObjectWrapper Instanzen.


-(NSArray*)createFilledArray{
    NSArray *result = [NSArray arrayWithObjects:
                       [ObjectWrapper createWithValue:[NSArray arrayWithObject:@"one"]],
                       [ObjectWrapper createWithValue:[NSNumber numberWithInt:5]], nil];
    return result;
}
#import "ObjectWrapper.h"

@implementation ObjectWrapper

+(ObjectWrapper*)createWithValue:(NSObject*)pValue{
    return [[[ObjectWrapper alloc] initWithValue:pValue] autorelease];
}

-(id)initWithValue:(NSObject*)pValue{
    self = [super init];
    if(self){
        _value = pValue;
        [_value retain];
    }
    return self;
}

-(void)dealloc{
    [super dealloc];
    [_value release];
    _value = nil;
}
@end

Die Ausgabe des Debuggers zeigt an, dass result zwei Elemente vom Datentyp ObjectWrapper enthält. Es kann aber nicht mehr nachvollzogen werden was der Inhalt des Arrays ist, welches der erste ObjectWrapper enthält. Auch der Wert des zweiten ObjectWrappers ist im Debugger nicht zu erkennen.
Xcode-Debugger Ausgabe

po – print object
Mithilfe des Konsolen-Befehls “po” können wir dem Debugger einiges an Informationen entlocken. Da auf die Werte der ObjectWrapper Instanzen nicht direkt von aussen zugegriffen werden kann (diese sind protected), erfragen wir den Wert via Speicheradresse, welche uns der Debugger liefert. Folgende Eingabe gibt uns den Wert des Integers zurück, den wir in der zweiten ObjectWrapper Instanz gespeichert hatten.

po 0x6b04950

Die Anzahl der Elemente im Array, welches die erste ObjectWrapper Instanz enthält, erfahren wir durch den Aufruf der Methode count von NSArray. Hier muss jedoch der Befehl “p” verwendet werden, da count einen primitiven Datentyp als Rückgabewert hat.

p (int)[0x6b045f0 count]

Doch wie kommen wir an die Länge des Strings, der sich im Array der ersten ObjectWrapper Instanz befindet? Ich gebe zu, dass dies in diesem Beispiel nicht allzuviel Sinn macht, aber es lässt die Möglichkeiten erahnen, welche dieser Befehl bietet. Folgender Aufruf gibt uns lediglich den String direkt aus.

po [0x6b045f0 objectAtIndex:0]

In diesem Fall hilft uns der Befehl “p” etwas weiter. Arrays enthalten Speicheradressen die auf Objekte verweisen. Mit folgender Eingabe können wir uns relativ einfach die Speicheradresse des Objektes besorgen.

p (int)[0x6b045f0 objectAtIndex:0]

Die Rückgabe kann nun dazu verwendet werden um die Länge des Strings zu erfragen.

p (int)[0x35e0 length]

Doch das ist noch lange nicht alles, was mit diesem Befehl alles ans Tageslicht befördert werden kann. Der Datentyp einer Instanz kann mit folgender Eingabe erfragt werden (vorausgesetzt es handelt sich um keinen primitiven Datentyp).

po [0x35e0 class]

Zusammenfassend kann man sagen, der Befehl “po” ist zwar recht nützlich, bietet aber nicht in allen Situationen ein befriedigendes Ergebnis. Es gibt noch eine ganze Reihe andere Befehle zu entdecken, die beim Debuggen hilfreich sein können. Und die investierte Zeit lohnt sich auf jeden Fall ;) . Für alle, die nun Lust bekommen haben das ganze selber einmal auszuprobieren, können sich hier das verwendete Beispiel herunterladen.

Posted in Objective-C, Tutorial, iPhone | Tagged , , , | Leave a comment

iOS: Skinning der UISegmentedControl-Komponente

Dieser Blogeintrag befasst sich mit dem Skinning der UISegmentedControl-Komponente, was sich mit iOS 5 nun einfacher gestaltet. So sieht die Komponente in ihren normalen Zustand aus:

default look
Seit iOS 5 werden die Methoden setBackgroundImage: forState: barMetrics: und setDividerImage: forLeftSegmentState: rightSegmentState: barMetrics: bereitgestellt, mit denen das Aussehen der Komponente im Handumdrehen verändert werden kann.

skinned UISegmentedControl
Der verwendete Code für die obrige Darstellung sieht folgendermaßen aus:

    // navigation is an UISegmentedControl
    // BACKGROUND
    // unselected
    UIImage *unselected = [[UIImage imageNamed:@"unselected.png"]
                           resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
    [[self navigation] setBackgroundImage:unselected
                                           forState:UIControlStateNormal
                                         barMetrics:UIBarMetricsDefault];

    // selected
    UIImage *selected = [[UIImage imageNamed:@"selected.png"]
                         resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
    [[self navigation] setBackgroundImage:selected
                                           forState:UIControlStateSelected
                                         barMetrics:UIBarMetricsDefault];

    // DIVIDER
    // unselected/unselected
    UIImage *unselectedUnselected = [UIImage imageNamed:@"unselected_unselected.png"];
    [[self navigation] setDividerImage:unselectedUnselected
                             forLeftSegmentState:UIControlStateNormal
                               rightSegmentState:UIControlStateNormal
                                      barMetrics:UIBarMetricsDefault];

    // selected/unselected
    UIImage *selectedUnselected = [UIImage imageNamed:@"selected_unselected.png"];
    [[self navigation] setDividerImage:selectedUnselected
                             forLeftSegmentState:UIControlStateSelected
                               rightSegmentState:UIControlStateNormal
                                      barMetrics:UIBarMetricsDefault];

    // unselected/selected
    UIImage *unselectedSelected = [UIImage imageNamed:@"unselected_selected.png"];
    [[self navigation] setDividerImage:unselectedSelected
                             forLeftSegmentState:UIControlStateNormal
                               rightSegmentState:UIControlStateSelected
                                      barMetrics:UIBarMetricsDefault];

Die Eigenschaften des Labels können mit der Methode setTitleTextAttributes: forState: verändert werden.

   // navigation is an UISegmentedControl
   // STATE NORMAL
    [navigation setTitleTextAttributes:
     [NSDictionary dictionaryWithObjectsAndKeys:
      [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0],
      UITextAttributeTextColor,
      [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.8],
      UITextAttributeTextShadowColor,
      [NSValue valueWithUIOffset:UIOffsetMake(0, -1)],
      UITextAttributeTextShadowOffset,
      nil] forState:UIControlStateNormal];

    // STATE SELECTED
    [navigation setTitleTextAttributes:
     [NSDictionary dictionaryWithObjectsAndKeys:
      [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0],
      UITextAttributeTextColor,
      [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.8],
      UITextAttributeTextShadowColor,
      [NSValue valueWithUIOffset:UIOffsetMake(0, -1)],
      UITextAttributeTextShadowOffset,
      nil] forState:UIControlStateSelected];

Das hier verwendete Beispiel verändert die Farbe der Labels für den normalen und selektierten Zustand.

skinned UISegmentedControl
Die verwendeten Grafiken für die Segmente selber sind nicht auf die Buttonform beschränkt. Hier ein Beispiel, in dem die Segmente eher an Tabs erinnern:

skinned UISegmentedControl

Das komplette Beispiel mit allen Grafiken und dem Code könnte ihr hier als Xcode-Projekt herunterladen.

Posted in Objective-C, iPhone | Tagged , , , , | Leave a comment

ItemRenderer: ab-/anmelden von EventListenern

Viele Beispiele im Netz zeigen, wie schnell und einfach sich ItemRenderer für Flex-Komponenten erstellen lassen. Leider sind diese Beispiele für größere Anwendungen kaum zu gebrauchen, da entweder die Performance durch die vielen Bindings verloren geht, oder verwendete EventListener nicht mehr abgemeldet werden.

Aber wann ist nun der richtige Zeitpunkt um EventListener in ItemRenderern wieder abzumelden? Da die meisten Flex-Komponenten die Renderer intern wiederverwenden (es wird lediglich die data-Eigenschaft des Renderers geändert), besteht die Möglichkeit bei Änderungen dieser Eigenschaft die Listener abzumelden.

private var _dataChanged:Boolean;

    /**
     * @inheritDoc
     */
    override public function set data(value:Object):void {
        if (data != value) {
            if (data) {
                removeDataListener();
            }
            super.data = value;
            _dataChanged = true;
            invalidateProperties();
        }
    }

    /**
     * @inheritDoc
     */
    override protected function commitProperties():void {
        super.commitProperties();

        if (_dataChanged) {
            _dataChanged = false;
            if (data) {
                addDataListener();
                // do other stuff here
            }
        }
    }

    protected function removeDataListener():void {
        // remove data listener
    }

    protected function addDataListener():void {
        // add data listener
    }

Leider ist dies nur die halbe Miete. Was passiert wenn die Flex-Komponente, die die Renderer verwendet, oder der Renderer selbst aus der DisplayList entfernt wird? Es existieren immer noch die Listener innerhalb des Renderers, der dadurch vor der GarbageCollection (GC) geschützt wird. Dies betrifft nur Listener die nicht als weak-Listener angemeldet wurden.

Aus diesem Grund melden wir uns für das Event “REMOVED_FROM_STAGE” an und sorgen dafür, dass alle Listener abgemeldet werden. Lediglich der Listener für das Event “REMOVED_FROM_STAGE” bleibt bestehen, da es vorkommen kann, dass eine Flex-Komponente ihre Renderer aus der DisplayList nimmt und später wiederverwendet. Der Renderer wird trotz des “REMOVED_FROM_STAGE”-Listeners von der GC entfernt, da dieser als weak-Listener angemeldet wurde.

package {

import flash.events.Event;
import mx.core.EventPriority;
import spark.components.supportClasses.ItemRenderer;

public class BaseItemRenderer extends ItemRenderer {

    private var _dataChanged:Boolean;

    /**
     * Constructor.
     */
    public function BaseItemRenderer() {
        addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage, false, EventPriority.DEFAULT, true);
    }

    /**
     * @inheritDoc
     */
    override public function set data(value:Object):void {
        if (data != value) {
            if (data) {
                removeDataListener();
            }
            super.data = value;
            _dataChanged = true;
            invalidateProperties();
        }
    }

    /**
     * @inheritDoc
     */
    override protected function commitProperties():void {
        super.commitProperties();

        if (_dataChanged) {
            _dataChanged = false;
            if (data) {
                addDataListener();
                // do other stuff here
            }
        }
    }

    // Should be used to free all unused data.
    protected function dispose():void {
        data = null;
        // do other stuff here
    }

    protected function removeDataListener():void {
        // remove data listener
    }

    protected function addDataListener():void {
        // add data listener
    }

    private function onRemovedFromStage(event:Event):void {
        dispose();
    }
}
}

Die endgültige Version des hier vorgestellten ItemRenderers könnt ihr hier herunterladen.

Posted in Actionscript, Flex | Tagged , , , | Leave a comment

PureMVC: EventMapMediator – automatisiertes Entfernen aller Eventlistener

Jeder Mediator im PureMVC Framework, der sich bei seiner Viewkomponente für Events anmeldet muss diese spätestens beim entfernen des Mediators auch wieder abmelden.
In der Regel sieht der Code im Mediator, für das An- und Abmelden, folgendermaßen aus:

public class LoginMediator extends Mediator
{
	override public function onRegister():void {
		view.addEventListener(Event.A, eventAHandler);
		view.addEventListener(Event.B, eventBHandler);
		...
		view.addEventListener(Event.Z, eventZHandler);
	}

	override public function onRemove():void {
		view.removeEventListener(Event.A, eventAHandler);
		view.removeEventListener(Event.B, eventBHandler);
		...
		view.removeEventListener(Event.Z, eventZHandler);
	}

	private function get view():DisplayObject{
		return DisplayObject(viewComponent);
	}
}

Dabei muss der Entwickler selbst darauf achten, dass er auch wirklich alle Eventlistener wieder abmeldet.
Das Framework Robotlegs bietet für diesen Fall einen automtisierten Ansatz, dort werden selbstständig alle registrierten Eventlistener abgemeldet sobald ein Mediator entfernt wird. Diese Funktionalität habe ich für das PureMVC Framework adaptiert und eine neue Mediatorklasse erstellt.
Alle Eventlistener die über die Methode “addViewListener” registriert werden, werden automatisch beim entfernen des Mediators Abgemeldet.
Sodass das vorherige Beispiel nun so aussieht:

public class LoginMediator extends EventMapMediator
{
	override public function onRegister():void {
		addViewListener(Event.A, eventAHandler);
		addViewListener(Event.B, eventBHandler);
		...
		addViewListener(Event.Z, eventZHandler);
	}

	private function get view():DisplayObject{
		return DisplayObject(viewComponent);
	}
}

Die EventMap selber ist nicht vom Mediator abhängig und kann somit auch in Viewkomponenten verwendet werden. Dort muss sich aber selber darum gekümmert werden, die Eventlistener auch wieder zu entfernen, um Memoryleaks zu vermeiden.

Die Klassen findet ihr hier als Zip-Datei zum Download.

Posted in Actionscript, Flex, MVC | Tagged , , | Leave a comment

Flex: Tree-Komponente mit unterschiedlichen ItemRenderern

Da die Tree-Komponente nur eine Instanz eines ItemRenderers intern zur Darstellung der Daten verwendet, muss dieser Renderer das Aussehen aller Baumelemente beinhalten. Dies kann dazu führen, dass der ItemRenderer sehr viel Code enthält, wenn für Verzweigungen und Blätter des Trees verschiedene Funktionalitäten und Aussehen gewünscht sind. Um dies zu vermeiden, habe ich die Tree-Komponente so erweitert, dass für jedes Baumelement ein anderer ItemRenderer verwendet werden kann.

package {
import mx.controls.Tree;
import mx.core.IFactory;

/**
 * Adds support for multiple itemRenderer.
 * The default implementation of the tree supports only one type of renderer for all items.
 */
public class ExtendedTree extends Tree {

    /**
     * This method is used to retrieve a factory which creates an itemRenderer for an object.
     * The signature of the function should look like: "myCustomFunction(data:Object):IFactory"
     */
    public var resolveItemRendererFunction:Function;

    /**
     * @inheritDoc
     */
    override public function getItemRendererFactory(data:Object):IFactory {
        if (resolveItemRendererFunction != null) {
            return resolveItemRendererFunction(data);
        } else {
            return super.getItemRendererFactory(data);
        }
    }
}
}

Hier sieht man den Unterschied zwischen der erweiterte Komponente (links), welche zwei verschiedene ItemRenderer verwendet, und der normalen Tree-Komponente (rechts):

Tree-Komponente

Das Beispiel aus den Screenshoots kann hier heruntergeladen werden.

Posted in Actionscript, Flex | Tagged , | Leave a comment

iOS-Entwicklung: BAD_ACCESS-Fehler mit NSZombieEnabled Debuggen

Jeder der schon einmal einen BAD_ACCESS-Fehler in einer Anwendung beheben musste, weiß wie umständlich dies sein kann. Da diese Art von Fehler an irgendeiner Stelle im Programm auftritt, und keinen Rückschluss auf den Code gibt der diesen Fehler verursacht hat.
Glücklicherweise gibt es aber die Umgebungsvariable NSZombieEnabled, die dafür sorgt das Objekte deren releaseCount gleich 0 ist nicht gelöscht werden. Wird im Laufe des Programms eine Nachricht an ein solches Objekt gesendet, bleibt der Debugger sofort stehen. Dadurch kann genau analysiert werden, an welcher Stelle im Code der fehlerhafte Zugriff erfolgte.

Erstellen der Variable (Xcode 4.1):
Mit gedrückter alt-Taste auf den Run-Button klicken.
Durch die gedrückte Taste öffnet sich ein Fenster in dem diverse Einstellungen vorgenommen werden können. Im Fenster auf den Reiter “Arguments” gehen und unter “Environment Variables” eine neue Variable mit dem Namen NSZombieEnabled erstellen und ihr den Wert YES zuweisen.

Nach dem Debuggen sollte diese Variable wieder entfernt oder zumindest auf FALSE gesetzt werden, da ansonsten Memoryleaks entstehen!

Posted in Objective-C, iPhone | Tagged , , , | Leave a comment

Flaschenhals: Binding

Ein sehr mächtiges Feature in Flex ist das [Bindable]-Metadata-Tag. Man sollte sich aber dennoch bewusst sein, dass auch das Binding einige Nachteile mit sich bringen kann. Wenn eine Klasse oder Properties einer Klasse mithilfe des [Bindable]-Tags für den Compiler markiert werden, generiert dieser aus der Property einen Getter und Setter. Im Setter wird dann ein PropertyChangeEvent versendet. Damit ein anderes Objekt über die Änderung informiert werden kann, wird diese vom Compiler als Empfänger für das PropertyChangeEvent angemeldet.

Da jede so markierte Property ein PropertyChangeEvent versendet, wird zusätzlich geprüft, welche Property geändert wurde. Wird ein Objekt nun an eine Property einer komplexen Klasse (viele [Bindable]-markierte Properties) gebunden, führt dies zu spürbaren Performanceeinbußen.

Dem kann jedoch Abhilfe geschaffen werden, indem im [Bindable]-Tag zusätzlich noch das Event definiert wird (z.B. [Bindable (event="xyChanged")]), welches bei einer Änderung der Property versendet wird. Leider generiert der Compiler nicht die entsprechende Benachrichtigung, so dass der Programmierer selber dafür sorgen muss, das Event zu versenden.

package
{
    import flash.events.EventDispatcher;
    import flash.events.Event;

    public class ExampleVO extends EventDispatcher
    {
        private var _description:String;

        [Bindable(event="descriptionChanged")]
        public function get description():String {
            return _description;
        }

        public function set description(value:String):void {
            if (_description !== value) {
                _description = value;
                if (hasEventListener("descriptionChanged")) {
                    dispatchEvent(new Event("descriptionChanged"));
                }
            }
        }
    }
}

Dies erfordert einige Zeilen mehr Code. In komplexen Anwendungen kann dadurch einiges an Performance gewonnen werden, da nun das ermitteln der geänderten Property entfällt.

Posted in Flex | Tagged , , | Leave a comment

Vector als “dataProvider” für Flex-View-Komponenten

Seit der Einführung des FlashPlayers 10, steht dem Entwickler die Klasse Vector zur Verfügung. Diese bringt gegenüber dem Array einige Vorteile mit sich, doch spätestens bei der Zuweisung eines Vectors als “dataProvider” für View-Komponenten steht der Entwickler vor einem Problem. Für die Array-Klasse existieren die Klassen ArrayList und ArrayCollection, die das Interface implementieren welches ein “dataProvider” benötigt. Wohingegen für die Klasse Vector keine Alternativen geboten werden.
Da ich mir selber mehr als einmal diese Möglichkeit gewünscht hätte, habe ich analog zum Array die Klassen VectorList und VectorCollection erstellt. Mit Hilfe dieser Wrapperklassen kann nun auch ein Vector als “dataProvider” verwendet werden.

<fx:Script>
	<![CDATA[
			import net.reppa.utils.collections.VectorCollection;
			import net.reppa.utils.collections.VectorList;

			private function initializeComboBox(vector:Vector.<int>):void{
				comboBox.dataProvider = new VectorCollection(vector);
				// or
				//comboBox.dataProvider = new VectorList(vector);
			}
	]]>
</fx:Script>

<s:ComboBox id="comboBox" />

Die Klassen findet ihr hier als Zip-Datei zum Download.

Posted in AIR, Actionscript, Flex | Tagged , , , | Leave a comment

Übergabe und Verarbeitung von eigenen Flex-Compiler-Argumenten (Compile-Time)

Dieser Eintrag ergänzt die Übergabe und Verarbeitung von eigenen Flex-Compiler-Argumenten um ein ANT-Buildscript für Flex4, bei dem die Compile-Time als Compilerargument übergeben wird.

Im ANT-Buildscript wird zuerst die aktuelle Zeit an eine ANT-Property “compiletime” übergeben:

	<tstamp>
		<format property="compiletime" pattern="dd.MM.yyyy, HH:mm:ss" />
	</tstamp>

Diese Property wird dann im mxml-Task per “define” als Compilerargument hinzugefügt:

	  <mxmlc file="${SRC_DIR}/Main.mxml" output="${DEPLOY_DIR}/Main.swf">
	  	<define name="CONFIG::COMPILE_TIME" value="'${compiletime}'"/>
	  	<load-config filename="${FLEX_HOME}/frameworks/flex-config.xml"/>
		<source-path path-element="${FLEX_HOME}/frameworks"/>
	  	<compiler.debug>false</compiler.debug>
	  </mxmlc>

Der Quellcode zu diesem Beispiel kann hier heruntergeladen werden.

Posted in Flex | Tagged , , | Leave a comment