Pixel-perfect accuracy with FXG?

Flex No Comments »

I’ve been running some tests in Flex rendering FXG rects with a stroke to see if there’s a way to get the strokes to render inside the bounds of the object. I need this precision for a project I’m working on and unfortunately it doesn’t look good. All of these tests were done using some simple FXG code …

  1. <s:Rect
  2.    
  3.    width="30"
  4.    height="30"
  5.    radiusX="4"
  6.    radiusY="4"
  7.    >
  8.    <s:fill>
  9.     <s:SolidColor color="#ffffff" />
  10.    </s:fill>
  11.    <s:stroke>
  12.     <s:SolidColorStroke color="#ff3333" alpha="1" pixelHinting="true" weight="2" />
  13.    </s:stroke>
  14.   </s:Rect>

I’m not too concerned about the rounded corners but the lack of precision is an issue if you intend to create dynamic skins and want some control over the positions and dimensions of your composition.

Take a look to see my results :

Main.html.png @ 1600% (Layer 0, RGB/8*)
Uploaded with plasq‘s Skitch!
Main.html-2.png @ 1600% (Layer 0, RGB/8*)
Uploaded with plasq‘s Skitch!
Main.html-3.png @ 600% (Layer 0, RGB/8*)
Uploaded with plasq‘s Skitch!

You may need to jump to Skitch for that last one but basically if you have four squares in a HGroup with a 4 pixel gap and the square is defined as 30 pixels in width then you would expect an overall width of 132 pixels. You will actually get 140. This could be a big problem. 8 pixels is a big discrepancy.

Flash Catalyst and the Designer/Developer workflow

Flex 1 Comment »
Designer/Developer workflow
Uploaded with plasq‘s Skitch!
DesignerDeveloperWorkflow: Art Director / Interaction Designer
Uploaded with plasq‘s Skitch!
DesignerDeveloperWorkflow: Interaction Designer / Developer
Uploaded with plasq‘s Skitch!
DesignerDeveloperWorkflow: Interaction Designer / Developer 2
Uploaded with plasq‘s Skitch!
DesignerDeveloperWorkflow: Interaction Designer / Developer 3
Uploaded with plasq‘s Skitch!

I drew this up recently to illustrate a proposed workflow for a project I’m working on. This is similar to the workflow as proposed by Adobe but with an extra level of responsibility on the Interaction Designer. This being the requirement to adapt their Catalyst work in Flash Builder. This, in my opinion, is a more realistic overview of how most Flex framework projects actually play out nowadays.

Flash Catalyst is not quite powerful enough at the moment to produce flexible, bespoke skins. The chances are you’ll need to build components that aren’t just button skins. You need to write your own SkinnableComponent classes with custom skin parts that resize dynamically etc etc.

I’m afraid, if you want to be a shit-hot Interaction Designer and need to build amazing, re-usable prototypes that can quickly be integrated into the final architecture then you’re going to need to think outside of the Catalyst and get your hands dirty with Spark and the skinning component life-cycle.

Good luck!

There’s a PDF of the images on this post here.

Archiving and Unarchiving objects in Objective-C

Apple, Objective-C, iPhone No Comments »

Woohoo … my first Objective-C related post!

Ok, so this is nothing new but I thought I’d log a couple of things that caught me out as a NOOB to the whole iPhone development malarky.

First off archiving your objects is a way of saving them to a property list file that sits on your device and can be unarchived the next time your app loads as a way to persist data locally on the device. Very handy it is too. Now there are better ways to manage data in Obj-C … CoreData for a start. This is a much more powerful way to persist data to a SQL lite database and allows you loads more freedom in what you can actually archive. There are limitations to the property list method you see. You can only archive NS objects and simple scalars so your class structure needs to compensate for this. The ‘plist’ objects you can archive are … NSArray, NSDictionary, NSString, NSNumber, NSDate and NSData along with their mutable subclasses. The scalars are int, float, double etc.

I don’t want to write a massive chapter on this and there’s loads of information online about the technical ins and outs of what’s happening behind the scenes. What I do want to do is share the classes I’ve used in a recent project and explain in basics what’s happening.

Firstly we’ll create the object we want to be archived. Any object that needs to be archived and is a subclass of NSObject must adopt the NSCoding protocol. Take a look at this object …

DataModel Object for archiving

  1. /* INTERFACE */
  2.  
  3. #define kfavesArray @"favesArray"
  4. #define kCurrentFave @"currentFave"
  5. #define kCurrentFaveIndex @"currentFaveIndexPath"
  6.  
  7. #import <Foundation/Foundation.h>
  8.  
  9. @class FaveVO;
  10.  
  11.  
  12. @interface DataModel : NSObject <NSCoding>
  13. {
  14.         NSMutableArray *favesArray;
  15.         FaveVO *currentFave;
  16.         int faveIndex;
  17. }
  18.  
  19. @property (nonatomic, retain) NSMutableArray *favesArray;
  20. @property (nonatomic, retain) FaveVO *currentFave;
  21. @property int faveIndex;
  22.  
  23. -(void)addNewFaveLocation:(FaveVO *)fave;
  24. -(void)createNewFaveVOFromFaveVO:(FaveVO *)fave;
  25. -(void)setFaveLocation:(int)index;
  26. -(void)removeFaveLocation;
  27.  
  28. @end
  29.  
  30. /* IMPLEMENTATION */
  31.  
  32. #import "DataModel.h"
  33. #import "FaveVO.h"
  34.  
  35. @implementation DataModel
  36.  
  37. @synthesize favesArray;
  38. @synthesize currentFave;
  39. @synthesize faveIndex;
  40.  
  41. //------------------------------------------------------
  42. -(id)init
  43. {
  44.         if(self = [super init])
  45.         {
  46.                 favesArray = [[NSMutableArray alloc] init];
  47.         }
  48.        
  49.         return self;
  50. }
  51. //--------------------------------------------------
  52. -(void)addNewFaveLocation:(FaveVO *)fave
  53. {
  54.         if(!favesArray) favesArray = [[NSMutableArray alloc] init];
  55.        
  56.         [favesArray insertObject:fave atIndex:0];
  57.         currentFave = [favesArray objectAtIndex:0];
  58.         faveIndex = 0;
  59.        
  60.         NSLog(@"DataModel currentFave: %@ faveIndex: %i", currentFave,faveIndex);
  61. }
  62. //--------------------------------------------------
  63. -(void)setFaveLocation:(int)index
  64. {
  65.         currentFave = [favesArray objectAtIndex:index];
  66.         faveIndex = index;
  67.        
  68.         NSLog(@"DataModel currentFave: %@ faveIndex: %i", currentFave,faveIndex);
  69. }
  70. //--------------------------------------------------
  71. -(void)removeFaveLocation
  72. {
  73.         currentFave = nil;
  74.        
  75.         NSLog(@"DataModel currentFave: %@ faveIndex: %i", currentFave,faveIndex);
  76. }
  77. //--------------------------------------------------
  78. -(void)createNewFaveVOFromFaveVO:(FaveVO *)fave
  79. {
  80.         double lat = fave.lat;
  81.         double lon = fave.lon;
  82.        
  83.         FaveVO *newFaveVO = [[FaveVO alloc] initWithLabel:[NSString stringWithString:fave.label]
  84.                                                                                 streetAddress:[NSString stringWithString:fave.streetAddress]
  85.                                                                                                  city:[NSString stringWithString:fave.city]
  86.                                                                                                 state:[NSString stringWithString:fave.state]
  87.                                                                                                   zip:[NSString stringWithString:fave.zip]
  88.                                                                                                   lat:lat
  89.                                                                                                   lon:lon];
  90.         [favesArray addObject:newFaveVO];
  91.         [newFaveVO release];
  92. }
  93. //--------------------------------------------------
  94. -(void)dealloc
  95. {
  96.         [favesArray release];
  97.         [currentFave release];
  98.         [super dealloc];
  99. }
  100. //--------------------------------------------------
  101.  
  102.  
  103. //--------------------------------------------------
  104. #pragma mark NSCoding
  105. - (void)encodeWithCoder:(NSCoder *)encoder
  106. {
  107.         [encoder encodeObject:favesArray forKey:kfavesArray];
  108.         [encoder encodeObject:currentFave forKey:kCurrentFave];
  109.         [encoder encodeInt:faveIndex forKey:kCurrentFaveIndex];
  110. }
  111. //--------------------------------------------------
  112. - (id)initWithCoder:(NSCoder *)decoder
  113. {
  114.         NSLog(@"DataModel initWithCoder: %@", decoder);
  115.        
  116.         if (self = [super init])
  117.         {
  118.                 self.favesArray = [decoder decodeObjectForKey:kfavesArray];
  119.                
  120.                 self.faveIndex = [decoder decodeIntForKey:kCurrentFaveIndex];
  121.                 self.currentFave = [favesArray objectAtIndex:faveIndex];
  122.         }
  123.         return self;
  124. }
  125.  
  126. @end

This object also references another object called FaveVO this is an object that I’m feeding into an array and also adopts the NSCoding protocol so you can see that you can archive quite a complicated object overall.

The important stuff is after the line that says “#pragma mark NSCoding”. These methods (using Actionscript terminology but I don’t care) handle what happens when the encoder and decoder go to work on your object.

- (void)encodeWithCoder:(NSCoder *)encoder
{
 [encoder encodeObject:favesArray forKey:kfavesArray];
 [encoder encodeObject:currentFave forKey:kCurrentFave];
 [encoder encodeInt:faveIndex forKey:kCurrentFaveIndex];
}

The encodeObject message takes each of my class variables and copies them into an NSData object using the keys which I defined in the interface. These are unique identifiers that I can use when I want to decode them later like so :

- (id)initWithCoder:(NSCoder *)decoder
{
 if (self = [super init])
 {
  self.favesArray = [decoder decodeObjectForKey:kfavesArray];
  self.faveIndex = [decoder decodeIntForKey:kCurrentFaveIndex];
  self.currentFave = [favesArray objectAtIndex:faveIndex];
 }
return self;
}

And that’s all you need to know about the Object you want to archive. Next comes the class that I’m using to do the archiving and unarchiving.

Here’s the class :

Archiving and Unarchiving DataModel Object

  1. /* INTERFACE */
  2.  
  3. #import <Foundation/Foundation.h>
  4.  
  5. #define kFilename       @"dataarchive.plist"
  6. #define kDataKey                @"Data"
  7.  
  8. @class DataModel;
  9. @interface DataManager : NSObject {
  10.  
  11.         DataModel *dataModel;
  12. }
  13.  
  14. @property (nonatomic, retain) DataModel *dataModel;
  15.  
  16. -(NSString *)dataFilePath;
  17. -(void)initDataModel;
  18. -(void)applicationWillTerminate:(NSNotification *)notification;
  19.  
  20. +(DataManager *)sharedDataManager;
  21.  
  22. @end
  23.  
  24. /* IMPLEMENTATION */
  25.  
  26. #import "DataManager.h"
  27. #import "SynthesizeSingleton.h"
  28. #import "DataModel.h"
  29. #import "FaveVO.h"
  30.  
  31. @implementation DataManager
  32.  
  33. SYNTHESIZE_SINGLETON_FOR_CLASS(DataManager);
  34.  
  35. @synthesize dataModel;
  36.  
  37. //-----------------------------------------------------------------------------------------------------
  38. - (NSString *)dataFilePath
  39. {
  40.         NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  41.         NSString *documentsDirectory = [paths objectAtIndex:0];
  42.         return [documentsDirectory stringByAppendingPathComponent:kFilename];
  43. }
  44. //-----------------------------------------------------------------------------------------------------
  45. -(void)initDataModel
  46. {
  47.         NSString *filePath = [self dataFilePath];
  48.        
  49.         if([[NSFileManager defaultManager] fileExistsAtPath:filePath])
  50.         {
  51.                 NSLog(@"FILE EXISTS");
  52.                
  53.                 NSData *data = [[NSMutableData alloc] initWithContentsOfFile:[self dataFilePath]];
  54.                 NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
  55.                
  56.                 DataModel *decodedDataModel = [unarchiver decodeObjectForKey:kDataKey];
  57.                
  58.                 dataModel = [[DataModel alloc] init];
  59.                
  60.                 NSEnumerator *enumerator;
  61.                 enumerator = [decodedDataModel.favesArray objectEnumerator];
  62.                
  63.                 FaveVO *decodedFaveVO;
  64.                
  65.                 while (decodedFaveVO = [enumerator nextObject])
  66.                 {
  67.                         [dataModel createNewFaveVOFromFaveVO:decodedFaveVO];
  68.                 }
  69.                
  70.                 int index = decodedDataModel.faveIndex;
  71.                 dataModel.faveIndex = index;
  72.                 dataModel.currentFave = [dataModel.favesArray objectAtIndex:index];
  73.                
  74.                 [unarchiver finishDecoding];
  75.                 [unarchiver release];
  76.                 [data release];
  77.         }
  78.         else
  79.         {
  80.                 NSLog(@"FILE DOES NOT EXIST");
  81.                
  82.                 dataModel = [[DataModel alloc] init];
  83.         }
  84.        
  85.         UIApplication *app = [UIApplication sharedApplication];
  86.         [[NSNotificationCenter defaultCenter] addObserver:self
  87.                                                                                          selector:@selector(applicationWillTerminate:)
  88.                                                                                                  name:UIApplicationWillTerminateNotification
  89.                                                                                            object:app];
  90.         
  91. }
  92. //-------------------------------------------------------------
  93. - (void)applicationWillTerminate:(NSNotification *)notification
  94. {
  95.         NSLog(@"Application will terminate");
  96.        
  97.         NSMutableData *data = [[NSMutableData alloc] init];
  98.         NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
  99.        
  100.         [archiver encodeObject:dataModel forKey:kDataKey];
  101.         [archiver finishEncoding];
  102.         [data writeToFile:[self dataFilePath] atomically:YES];
  103.        
  104.         [archiver release];
  105.         [data release];
  106. }
  107.  
  108.  
  109. //--------------------------------------------------
  110. -(void)dealloc
  111. {
  112.         [dataModel release];
  113.         [super dealloc];
  114. }
  115.  
  116.  
  117. @end

The initDataModel method looks for the property list and then uses the NSKeyedUnarchiver object to decode the objects. When the application terminates the objects are encoded using the NSKeyedArchiver object. The class listens for the ApplicationWillTerminate notification using the NSNotificationCenter object. This is similar to the Actionscript event model that uses listeners. The NSEnumerator object is used to step through an array of objects. The decoded FaveVO objects are then sent through to my DataModel class and passed to the createNewFaveVOFromFaveVO method which is where the objects are re-created as shiny new objects. Remember the NSKeyedUnarchiver object is released so you need to retain the objects you need … usually better to create new ones within the correct scope.

Another custom Flex preloader but with RSL support

Actionscript, Flex 1 Comment »

This post has a great article on building a custom Flex preloader which also allows you to handle the progress of the runtime shared libraries that the Flex framework now loads into the users cache.

The DownloadProgressBar component allows you to view the progress of the runtime shared libraries but be aware that it can only tell you about the RSL as it loads and as there may be more than one it is practically impossible to get any indication of how many bytes in total the app is going to load. For this reason you may have seen erratic preloaders out there at the moment using all kinds of colourful language to tell you what it’s doing … “loading assets”, “modules”, “opening the champagne” etc. This is all done to keep the user occupied while each of the elements are loaded.

Then there’s the actual SWF file. Thankfully there are separate events to handle this so if you prefer you can still display the progress of the main SWF and then when it is completed change your display to inform the user that you are “initialising” which shouldn’t be too long after the main SWF has finished. Indeed your dependencies may have finished before the SWF.

I guess the only way to do this accurately is to know up front exactly how many bytes your application is going to load and then as you get the information from ProgressEvent.bytesLoaded from both the SWF and the RSL you can adjust your percentage calculation accordingly. If anyone does know how you can programmatically detect this I’d love to hear it.

Thanks to Bernhard Hirschmann and Jesse Warden for the work they’ve done and purely for my own reference here’s the class …

Custom Preloader for Flex with RSL support

  1. package
  2. {
  3.     import flash.display.MovieClip;
  4.     import flash.display.Sprite;
  5.     import flash.events.Event;
  6.     import flash.events.ProgressEvent;
  7.    
  8.     import mx.events.FlexEvent;
  9.     import mx.events.RSLEvent;
  10.     import mx.preloaders.DownloadProgressBar;
  11.  
  12.     /**
  13.      * This class extends the lightweight DownloadProgressBar class.  This class
  14.      * uses an embedded Flash 8 MovieClip symbol to show preloading.
  15.      * For handling both, SWF and RSL download, it separated its download progress
  16.      * by handling the appropriate events, i.e. RSLEvent
  17.      *
  18.      * @author jessewarden
  19.      * @author Bernhard Hirschmann (http://coding.bhirschmann.de)
  20.      */   
  21.     public class Preloader extends DownloadProgressBar
  22.     {
  23.        
  24.         /**
  25.          * The Flash 8 MovieClip embedded as a Class.
  26.          */       
  27.         [Embed(source="../assets/flash/preloader.swf", symbol="Preloader")]
  28.         private var FlashPreloaderSymbol:Class;
  29.        
  30.         private var clip:MovieClip;
  31.        
  32.     private var    isRslDownloading:Boolean = false;
  33.    
  34.         private var rslPercent:Number = 0;
  35.         private var swfPercent:Number = 0;
  36.        
  37.         private var rslBytesTotal:Number;
  38.         private var rslBytesLoaded:Number = 0;
  39.        
  40.         private var swfBytesTotal:Number;
  41.         private var swfBytesLoaded:Number = 0;
  42.        
  43.         public function Preloader()
  44.         {
  45.             super();
  46.            
  47.             // instantiate the Flash MovieClip, show it, and stop it.
  48.             // Remember, AS2 is removed when you embed SWF's,
  49.             // even "stop();", so you have to call it manually if you embed.
  50.             clip = new FlashPreloaderSymbol();
  51.             addChild(clip);
  52.             clip.gotoAndStop("start");
  53.         }
  54.        
  55.        public override function set preloader(preloader:Sprite):void
  56.     {                   
  57.       trace("starting...");
  58.  
  59.       // runtime shared library
  60.       preloader.addEventListener( RSLEvent.RSL_PROGRESS, onRSLDownloadProgress );
  61.       preloader.addEventListener( RSLEvent.RSL_COMPLETE, onRSLDownloadComplete );
  62.       preloader.addEventListener( RSLEvent.RSL_ERROR, onRSLError );
  63.  
  64.       // application
  65.       preloader.addEventListener( ProgressEvent.PROGRESS, onSWFDownloadProgress );   
  66.       preloader.addEventListener( Event.COMPLETE, onSWFDownloadComplete );
  67.      
  68.       // initialization
  69.       preloader.addEventListener( FlexEvent.INIT_PROGRESS, onFlexInitProgress );
  70.       preloader.addEventListener( FlexEvent.INIT_COMPLETE, onFlexInitComplete );
  71.      
  72.       clip.preloader.rsl_amount_txt.text = clip.preloader.app_amount_txt.text = "0%";
  73.      
  74.       centerPreloader();
  75.     }
  76.        
  77.     /**
  78.      * Makes sure that the preloader is centered in the center of the app.
  79.      *
  80.      */       
  81.     private function centerPreloader():void
  82.         {
  83.             x = (stageWidth / 2) - (clip.width / 2);
  84.             y = (stageHeight / 2) - (clip.height / 2);
  85.         }
  86.        
  87.     /**
  88.      * Updates the progress bar.
  89.      */
  90.     private function updateProgress():void
  91.     {
  92.       var p:Number = Math.round( (rslPercent + swfPercent) / 2 );
  93.         clip.preloader.gotoAndStop(p);
  94.     }
  95.    
  96.         /**
  97.          * As the RSL (runime shared library) (frame 2 usually) downloads, this event gets called.
  98.          * You can use the values from this event to update your preloader.
  99.          * @param event
  100.          *
  101.          */       
  102.         private function onRSLDownloadProgress( event:ProgressEvent ):void
  103.     {
  104.         isRslDownloading = true;
  105.        
  106.         rslBytesTotal = event.bytesTotal;
  107.         rslBytesLoaded = event.bytesLoaded;
  108.         rslPercent = Math.round( (rslBytesLoaded / rslBytesTotal) * 100);
  109.         trace("onRSLDownloadProgress: rslBytesLoaded " + rslBytesLoaded);
  110.         trace("onRSLDownloadProgress: rslBytesTotal " + rslBytesTotal);
  111.         trace("onRSLDownloadProgress: " + rslPercent + "%");
  112.         clip.preloader.rsl_amount_txt.text = String(rslPercent) + "%";
  113.        
  114.         updateProgress();
  115.     }
  116.    
  117.     /**
  118.      * When the download of frame 2
  119.      * is complete, this event is called. 
  120.      * This is called before the initializing is done.
  121.      * @param event
  122.      *
  123.      */       
  124.     private function onRSLDownloadComplete( event:RSLEvent ):void
  125.     {
  126.         trace("onRSLDownloadComplete: 100% - bytes total: " + event.bytesTotal);
  127.            clip.preloader.gotoAndStop(100);
  128.         clip.preloader.rsl_amount_txt.text = "100%";
  129.         rslPercent = 100;
  130.     }
  131.  
  132.     private function onRSLError( event:RSLEvent ):void
  133.     {
  134.         trace("onRSLError: " + event.errorText + " - " + event.url);
  135.         clip.preloader.status_txt.text = event.errorText;
  136.     }
  137.  
  138.         /**
  139.          * As the SWF (frame 2 usually) downloads, this event gets called.
  140.          * You can use the values from this event to update your preloader.
  141.          * @param event
  142.          *
  143.          */       
  144.         private function onSWFDownloadProgress( event:ProgressEvent ):void
  145.     {
  146.         swfBytesTotal = event.bytesTotal;
  147.         swfBytesLoaded = event.bytesLoaded;
  148.        
  149.         if ( isRslDownloading ) {
  150.           // as soon as RSL starts downloading the SWF data are added by the RSL values
  151.           swfBytesTotal -= rslBytesTotal;
  152.           swfBytesLoaded -= rslBytesLoaded;
  153.         }
  154.         swfPercent = Math.round( (swfBytesLoaded / swfBytesTotal) * 100);
  155.         trace("onSWFDownloadProgress: " + swfPercent + "%");
  156.         trace("onSWFDownloadProgress: swfBytesLoaded " + swfBytesLoaded);
  157.         trace("onSWFDownloadProgress: swfBytesTotal " + swfBytesTotal);
  158.         clip.preloader.app_amount_txt.text = String(swfPercent) + "%";
  159.  
  160.         updateProgress();
  161.     }
  162.    
  163.     /**
  164.      * When the download of frame 2
  165.      * is complete, this event is called. 
  166.      * This is called before the initializing is done.
  167.      * @param event
  168.      *
  169.      */       
  170.     private function onSWFDownloadComplete( event:Event ):void
  171.     {
  172.         trace("onSWFDownloadComplete: 100%");
  173.            clip.preloader.gotoAndStop(100);
  174.         clip.preloader.app_amount_txt.text = "100%";
  175.         swfPercent = 100;
  176.     }
  177.    
  178.     /**
  179.      * When Flex starts initilizating your application.
  180.      * @param event
  181.      *
  182.      */       
  183.     private function onFlexInitProgress( event:FlexEvent ):void
  184.     {
  185.         //trace("onFlexInitProgress: Initializing...");
  186.         try {
  187.           clip.preloader.gotoAndStop(100);
  188.           clip.preloader.status_txt.text = "Initializing...";
  189.         }
  190.         catch (e:Error) {
  191.         }
  192.     }
  193.    
  194.     /**
  195.      * When Flex is done initializing, and ready to run your app,
  196.      * this function is called.
  197.      *
  198.      * You're supposed to dispatch a complete event when you are done.
  199.      * I chose not to do this immediately, and instead fade out the
  200.      * preloader in the MovieClip.  As soon as that is done,
  201.      * I then dispatch the event.  This gives time for the preloader
  202.      * to finish it's animation.
  203.      * @param event
  204.      *
  205.      */       
  206.     private function onFlexInitComplete( event:FlexEvent ):void
  207.     {
  208.         trace("onFlexInitComplete");
  209.         clip.addFrameScript(21, onDoneAnimating);
  210.         clip.gotoAndPlay("fade out");
  211.     }
  212.    
  213.     /**
  214.      * If the Flash MovieClip is done playing it's animation,
  215.      * I stop it and dispatch my event letting Flex know I'm done.
  216.      * @param event
  217.      *
  218.      */       
  219.     private function onDoneAnimating():void
  220.     {
  221.         trace("onDoneAnimating");
  222.         clip.stop();
  223.         dispatchEvent( new Event( Event.COMPLETE ) );
  224.     }
  225.        
  226.     }
  227. }

Custom Preloader class for Flex apps

Actionscript, Flex No Comments »

I’ve blogged on this before but here’s an updated version of a custom preloader class that extends Sprite and loads in a MovieClip from a SWC to use as the animation and display the load progress.

Custom preloader for Flex

  1. package com.cravens.nr.preloader
  2. {
  3.         import com.cravens.nr.model.Config;
  4.        
  5.         import flash.display.GradientType;
  6.         import flash.display.Sprite;
  7.         import flash.events.Event;
  8.         import flash.events.ProgressEvent;
  9.         import flash.events.TimerEvent;
  10.         import flash.geom.Matrix;
  11.         import flash.utils.Timer;
  12.        
  13.         import mx.events.FlexEvent;
  14.         import mx.preloaders.IPreloaderDisplay;
  15.  
  16.         public class NRPreloader extends Sprite implements IPreloaderDisplay
  17.         {
  18.                 protected var loadingPanel:BusyPanel;
  19.                 protected var timer:Timer;
  20.                
  21.                 public function NRPreloader()
  22.                 {
  23.                         super();
  24.                 }
  25.                 //------------------------------------------------------------------------------
  26.                 public function set preloader(preloader:Sprite):void
  27.                 {
  28.                         preloader.addEventListener(ProgressEvent.PROGRESS, handleProgress);
  29.                         preloader.addEventListener(Event.COMPLETE, handleComplete);
  30.                        
  31.                         preloader.addEventListener(FlexEvent.INIT_PROGRESS, handleInitProgress);
  32.                         preloader.addEventListener(FlexEvent.INIT_COMPLETE, handleInitComplete);
  33.                 }
  34.                 //------------------------------------------------------------------------------
  35.                 public function initialize():void
  36.                 {
  37.                         var matrix:Matrix = new Matrix(0,1,1,0,0,0);
  38.                        
  39.                         graphics.beginGradientFill(GradientType.LINEAR,[0x000000,0x89265a],[1,1],[125,205],matrix);
  40.                         graphics.drawRect(0,0,Config.VIEWPORT_WIDTH,Config.VIEWPORT_HEIGHT);
  41.                        
  42.                         loadingPanel = new BusyPanel();
  43.                        
  44.                         try
  45.                         {
  46.                                 loadingPanel.heading_txt.text = "LOADING ... PLEASE WAIT";
  47.                                 loadingPanel.message_txt.text = "0/100%";
  48.                         }
  49.                         catch(e:Error)
  50.                         {
  51.                                 throw new Error("Asset does not contain the relevant textField objects");
  52.                         }
  53.                         finally
  54.                         {
  55.                                 loadingPanel.x = Config.VIEWPORT_CENTER_X-150;
  56.                                 loadingPanel.y = Config.VIEWPORT_CENTER_Y;
  57.                                
  58.                                 addChild(loadingPanel);
  59.                         }
  60.                 }
  61.  
  62.                
  63.                 //------------------------------------------------------------------------------
  64.                 // Define empty event listeners.
  65.                 private function handleProgress(event:ProgressEvent):void
  66.                 {
  67.                         var prog:Number = Math.ceil((event.bytesLoaded/event.bytesTotal)*100);
  68.                        
  69.                         loadingPanel.message_txt.text = prog+"/100%";
  70.                 }
  71.                 //------------------------------------------------------------------------------
  72.                 private function handleComplete(event:Event):void
  73.                 {
  74.                         loadingPanel.message_txt.text = "LAUNCHING";
  75.                 }
  76.                 //------------------------------------------------------------------------------
  77.                 private function handleInitProgress(event:FlexEvent):void
  78.                 {
  79.                 }
  80.                 //------------------------------------------------------------------------------
  81.                 private function handleInitComplete(event:Event):void
  82.                 {
  83.                         timer = new Timer(500,1);
  84.                         timer.addEventListener(TimerEvent.TIMER, dispatchComplete);
  85.                         timer.start();     
  86.                 }
  87.                
  88.                 //------------------------------------------------------------------------------
  89.                 private function dispatchComplete(event:TimerEvent):void
  90.                 {
  91.                         timer.removeEventListener(TimerEvent.TIMER, dispatchComplete);
  92.                         timer = null;
  93.                         dispatchEvent(new Event(Event.COMPLETE));
  94.                 }
  95.                
  96.                 //------------------------------------------------------------------------------
  97.                 // Implement IPreloaderDisplay interface
  98.                
  99.                 public function get backgroundColor():uint
  100.                 {
  101.                         return 0;
  102.                 }
  103.                 //------------------------------------------------------------------------------
  104.                 public function set backgroundColor(value:uint):void
  105.                 {
  106.                
  107.                 }
  108.                 //------------------------------------------------------------------------------
  109.                 public function get backgroundAlpha():Number
  110.                 {
  111.                         return 0;
  112.                 }
  113.                 //------------------------------------------------------------------------------
  114.                 public function set backgroundAlpha(value:Number):void
  115.                 {
  116.                 }
  117.                 //------------------------------------------------------------------------------
  118.                 public function get backgroundImage():Object
  119.                 {
  120.                         return undefined;
  121.                 }
  122.                 //------------------------------------------------------------------------------
  123.                 public function set backgroundImage(value:Object):void {
  124.                 }
  125.                 //------------------------------------------------------------------------------
  126.                 public function get backgroundSize():String {
  127.                         return "";
  128.                 }
  129.                 //------------------------------------------------------------------------------
  130.                 public function set backgroundSize(value:String):void {
  131.                 }
  132.                 //------------------------------------------------------------------------------
  133.                 public function get stageWidth():Number
  134.                 {
  135.                         return Config.VIEWPORT_WIDTH;
  136.                 }
  137.                 //------------------------------------------------------------------------------
  138.                 public function set stageWidth(value:Number):void
  139.                 {
  140.                 }
  141.                 //------------------------------------------------------------------------------
  142.                 public function get stageHeight():Number
  143.                 {
  144.                         return Config.VIEWPORT_HEIGHT;
  145.                 }
  146.                 //------------------------------------------------------------------------------
  147.                 public function set stageHeight(value:Number):void
  148.                 {
  149.                 }
  150.         }
  151. }

Also, notice I’ve created a simple grad background using the Sprite graphics …

  1. var matrix:Matrix = new Matrix(0,1,1,0,0,0);  
  2. graphics.beginGradientFill(GradientType.LINEAR,[0x000000,0x89265a],[1,1],[125,205],matrix);
  3. graphics.drawRect(0,0,Config.VIEWPORT_WIDTH,Config.VIEWPORT_HEIGHT);

This is the best way to do things because remember, the framework has not loaded while the preloader is displayed so you need to keep things lightweight and simple.

Also, the Matrix object rotates the grad so it runs vertically.

Have fun.

Detecting when text has run into the end of a container in a TextFlow object

Actionscript, Flex 3 Comments »

More fun and games with the Text Layout Framework. So, I have a load of links being flowed one by one through a bunch of linked containers. I need to detect when a link reaches the end and has ‘overflowed’ so to speak.

To do this you need to make sure your ContainerController objects have their ScrollPolicy set to OFF so the flowComposer recognises that the text is not in the container anymore. This will not halt the flow however so you need to add a nice bit of code that will detect if the FlowElement is no longer within a container.

Here’s the code …

Detecting when text has run into the end of the ContainerController in a TextFlow object

  1. /*
  2. A good idea to set a config object to pass into the TextFlow object when you create it
  3. */
  4. config = Configuration(TextFlow.defaultConfiguration).clone();
  5.  
  6. /*
  7. And add this overflowPolicy to it as well as your formatting defaults
  8. */
  9. config.overflowPolicy = OverflowPolicy.FIT_DESCENDERS;
  10.  
  11. textFlow = new TextFlow(config);
  12.  
  13. /*
  14.   Create your containers from sprites and make sure the scrollPolicy is OFF
  15. */
  16. var controller:ContainerController = new ContainerController(containerSprite,width,height);
  17. controller.verticalScrollPolicy = controller.horizontalScrollPolicy = ScrollPolicy.OFF;
  18.                                        
  19. textFlow.flowComposer.addController(controller);
  20.  
  21. /*
  22. Now imagine we are in a loop which adds a paragraph of lorem ipsum to the textflow and updates the controllers and checks if the flow is now out of the bounds of the container
  23. */
  24.  
  25. while(LOOP)
  26. {
  27.   textFlow.addChild(myParagraphElement); // you've created the paragraph
  28.   textFlow.flowComposer.updateAllControllers(); // you've created the controllers
  29.  
  30.   var containerIndex:int = textFlow.flowComposer.findControllerIndexAtPosition(textFlow.textLength-1);
  31.   if(containerIndex==-1) textFlowCompleteHandler();
  32. }

It’s these two lines that do the detection …

  1.  
  2. var containerIndex:int = textFlow.flowComposer.findControllerIndexAtPosition(textFlow.textLength-1);
  3.  
  4.   if(containerIndex==-1) textFlowCompleteHandler();

The findControllerIndexAtPosition method will return -1 if the character index passed in is not within a container. Remember to use textFlow.textLength-1 to get the last character. The textFlow variable is an instance of the TextFlow object.

The textFlowCompleteHandler can be any method that stops the insertion of FlowElement objects happening.

Adobe vs Apple? Sod the developers … what does the user want?

Apple, Flash, Web tools, iPhone, rant No Comments »

There’s been all kinds of heated debate and colorful invective surrounding the latest events in the Adobe vs Apple altercation. It’s hard for me to take sides because as a Flash developer for over ten years now and as a loyal Mac user for nearly 20 I’m finding that I do sympathise with both sides.

I find it distasteful that Apple should introduce section 3.3.1 merely days before the release of CS5 and Adobe’s new ‘compile Flash to iPhone app’ feature. It’s really quite underhand and will effect a lot of people’s livelihoods and damage business. Let’s not forget it’s not just Adobe that will be affected by this change. Unity3D and other 3rd party tools will not be able to release apps on the store. Some great apps like the ‘Star Wars Trench Run’ may need to be removed and as I understand it with my limited knowledge of these things some of the larger publishers like EA may also be affected as their games use 3rd party interpreters and tools that will be outlawed by Apple’s new rules.

On the other side though, Adobe should never have introduced this feature without the approval of Apple. They rushed this thing out and in the process have gambled with the livelihoods of hundreds of developers who have put their stake in iPhone app development with Flash.

Basically I think both parties have been very arrogant and have damaged a lot of reputations in the industry … mainly developers. We’ve got decent, honest people at each others throats when all they want to do is work together to make amazing, engaging content.

But the biggest victim in all this is the user. People buy iPhones and wonder why they can’t see the rich content they’ve come to expect. They struggle trying to view Flash content in the browser that hasn’t been built properly or they haven’t got the correct plug-in installed. What does the user want? Why is it all going wrong?

Fifteen years ago I was building CD-Rom titles in Director 3 which at the time was owned by Macromedia. We started using Flash 2 when it was released so we could include animated SWF content in our Director titles. We then started building Flash content for the web. At the time there was no such thing as ‘web standards’ … HTML was pretty basic and browsers were packed full of plug-in’s developed so you get the most out of your browsing experience. You had the ‘Real’ or ‘Quicktime’ plugin to watch video, ‘Shockwave’ allowed you to play amazing games online and ‘Flash’ exploded all over the web because it brought the browser to life and allowed us to build truly engaging rich content without loading new pages to update content.

In the present day we have a completely different type of internet. Web standards have evolved to the point that we now have a perfectly reasonable alternative to Flash integrated into the browser. HTML5 and WebGL allows us to build rich engaging content without a plug-in. In fact Flash is probably one of the few plugins people are still required to install into their browser to view a vast majority of content. And this is where Adobe have failed the user. Director died a death because people didn’t need to publish CD-Roms anymore and Flash should have evolved so the user didn’t need to use a plug-in in their browser.

Adobe should have adapted Flash so it output HTML5 and WebGL 3D content. The AIR runtime is fine on the desktop (although I will always choose a native Mac OS app) and will be just fine on the Android platform, hell they might even integrate it into their OS. But it’s in the browser they’ve failed and if they want to see Flash content on the iPhone they need to improve their tools (like Flash or Dreamweaver) so they produce standards compliant, rich, interactive content in the browser natively. This, after all, is what the user wants.

And Steve, stop being so evil. You’re forgetting what Apple was supposed to stand for. You should watch your 1984 Keynote speech every morning to remind yourself.

Some links on the subject …

http://www.smashingmagazine.com/2010/04/12/the-gradual-disappearance-of-flash-websites/

http://www.devwhy.com/blog/2010/4/12/its-all-about-the-framework.html?lastPage=true#comment8034519

http://mashable.com/2010/04/10/steve-jobs-adobe/

http://theflashblog.com/?p=1888

http://blog.codecomputerlove.com/2010/04/09/have-apple-crushed-cs5-flash-to-iphone-opportunity/

UPDATE: Apparently Flash to HTML5 was showcased during a ‘sneak peek’ session at MAX 2009 but has not made it into the new CS5 release. This is a real shame … Adobe could have made some real progress there.

UPDATE UPDATE: Apple have already started pulling apps. Scratch is an education tool built by MIT … it’s now not educating children in computer science.

Apple also banned this app for being too satirical!

Crazy code

Actionscript, Flex No Comments »

Sometimes you have to write a method for something crazy complicated. In this snippet I had a container with a load of TextFlow text in it and I needed to zoom into a specific link within the flow while also scaling the container and positioning it within a Scroller viewport without the container going out of the bounds of the Viewport when the link is centered within it.

This is meaningless to anyone but me but sometimes you just have to share the pain.

Zoom in to a link in a TextFlow within a scaled container in a Scroller viewport

  1. private function moveAndZoomToLink(link:LinkElement,p:Person):void
  2.                         {
  3.                                 var linkStart:int = link.getAbsoluteStart();
  4.                                 var textFlowLine:TextFlowLine = textFlow.flowComposer.findLineAtPosition(linkStart);
  5.                                 var linkPositionOnLine:int = linkStart-textFlowLine.absoluteStart;
  6.                                        
  7.                                 var controllerIndex:int = textFlow.flowComposer.findControllerIndexAtPosition(linkStart);
  8.                                 var controller:ContainerController = textFlow.flowComposer.getControllerAt(controllerIndex);
  9.                                 var containerSprite:Sprite = controller.container;
  10.                                
  11.                                 var rect:Rectangle = textFlowLine.getBounds();
  12.                                 var atomRect:Rectangle = textFlowLine.getTextLine().getAtomBounds(linkPositionOnLine+1);
  13.                                 var nameWidth:Number = p.firstName.length*atomRect.width;
  14.                                
  15.                                 var globalAtomRect:Rectangle = new Rectangle(rect.x+(atomRect.x-atomRect.width),rect.y,nameWidth,rect.height);
  16.                                
  17.                                 var box:Shape = new Shape();
  18.                                 box.graphics.beginFill(0xff0000,0.3);
  19.                                 box.graphics.drawRect(globalAtomRect.x,globalAtomRect.y,globalAtomRect.width,globalAtomRect.height);
  20.                                 containerSprite.addChild(box);
  21.                                
  22.                                 var localPosition:Point = new Point();
  23.                                 localPosition.x = containerSprite.x+rect.x+globalAtomRect.x;
  24.                                 localPosition.y = containerSprite.y+rect.y;
  25.                                
  26.                                 /*
  27.                                         We're zooming in so scale the local position
  28.                                 */
  29.                                 var scaledX:Number = localPosition.x*Config.SHIRT_ZOOM_SCALE;
  30.                                 var scaledY:Number = localPosition.y*Config.SHIRT_ZOOM_SCALE;
  31.                                
  32.                                 /*
  33.                                         We don't want the shirt to move so it is out of the viewport so calculate what this range is ...
  34.                                 */
  35.                                
  36.                                 var maxMoveX:Number = (Config.SHIRT_WIDTH*Config.SHIRT_ZOOM_SCALE)-Config.VIEWPORT_WIDTH;
  37.                                 var maxMoveY:Number = (Config.SHIRT_HEIGHT*Config.SHIRT_ZOOM_SCALE)-Config.VIEWPORT_HEIGHT;
  38.                                
  39.                                 var newX:Number = 0-scaledX;
  40.                                 var newY:Number = 0-scaledY;
  41.                                
  42.                                 var centeredX:Number = newX+Config.VIEWPORT_CENTER_X;
  43.                                 var centeredY:Number = newY+Config.VIEWPORT_CENTER_Y;
  44.                                
  45.                                 /*
  46.                                         Check the shirt hasn't moved out of bounds
  47.                                 */
  48.                                
  49.                                 var offsetX:Number = centeredX>0? centeredX*-1 : centeredX<0-maxMoveX? (centeredX*-1)-maxMoveX : 0;
  50.                                 var offsetY:Number = centeredY>0? centeredY*-1 : centeredY<0-maxMoveY? (centeredY*-1)-maxMoveY : 0;
  51.                                
  52.                                 /*
  53.                                         Finally, the new position is nudged so it is at the centre of the viewport space or as near as can be.
  54.                                 */
  55.                                 moveToPointX = centeredX>0? 0 : centeredX<0-maxMoveX? centeredX+((centeredX*-1)-maxMoveX) : centeredX;
  56.                                 moveToPointY = centeredY>0? 0 : centeredY<0-maxMoveY? centeredY+((centeredY*-1)-maxMoveY) : centeredY;
  57.                                
  58.                                 var viewportX:Number = Config.VIEWPORT_CENTER_X+offsetX;
  59.                                 var viewportY:Number = Config.VIEWPORT_CENTER_Y+offsetY;
  60.                                
  61.                                 shirtMoveTo.play();
  62.                                
  63.                                 var evt:PersonSelectionEvent = new PersonSelectionEvent(PersonSelectionEvent.PERSON_SELECTION,true);
  64.                                 evt.viewportPoint = new Point(viewportX,viewportY);
  65.                                 evt.person = p;
  66.                                
  67.                                 dispatchEvent(evt);
  68.                         }

Snippety Snipplr snip!

Actionscript No Comments »

So I’ve been using Snipplr recently for storing little code snippets. Looks like a pretty good service but after I tested its embedding features recently in a couple of posts I got sad and started to cry. This is because it didn’t embed very well. Not very well at all. You couldn’t scroll the code horizontally to read it.

Well, I’ve now installed the Snipplr wordpress plugin so hopefully the next bunch of stuff you see will be a couple of Snipplr snippets nicely embedded with some lovely scrolly-pollys scrollbars. Ok, I’ll stop talking like a baby now.

Get the server/domain name that your SWF is running on

  1. /*
  2. In Flex the Application needs to be complete so run this code within your applicationComplete event handler and pass a Boolean into a Data model somewhere so you can use it elsewhere.
  3. */
  4.  
  5. var Flex3Application:String = URLUtil.getServerName(Application.application.loaderInfo.url);
  6.  
  7. var Flex4Application:String = URLUtil.getServerName(FlexGlobals.topLevelApplication.loaderInfo.url);

Detecting if your SWF is running over HTTP or locally

  1. /*
  2. In Flex the Application needs to be complete so run this code within your applicationComplete event handler and pass a Boolean into a Data model somewhere so you can use it elsewhere.
  3. */
  4.  
  5. var isLocal:Boolean = loaderInfo.url.indexOf("file") == 0;

There’s a TextMate bundle too.

Exciting ay? The fun never stops here!

How to install the AIR 2 Beta SDK into FlashBuilder 4 and build a cool multi-touch app for your MacBook

Actionscript, Flex No Comments »

Phew … that was a long blog post title! I have to be very specific about this little demo though because if you aren’t on a MacBook with a multi-touch trackpad this demo will not do anything and then you’ll be severely pissed! I’ve not done any tests with Windows so I’m not sure about touch screens or other AIR enabled devices. More information on this here.

Firstly, go and download the latest AIR 2 SDK Beta from here.

The first thing you need to do is duplicate your FB4 SDK folder which you will find in the Application/Adobe Flash Builder 4/sdks/ folder. Then copy the tbz2 download into that folder.

4.0.0 AIR 2 Beta
Uploaded with plasq‘s Skitch!

Now you need to uncompress the tbz2 (zip) to the duplicated SDK folder you created. You’ll need to do this in the Terminal app so open it and type ‘cd’ to change the directory and then drag the folder into the terminal to add the path. Then hit return to jump into that folder and enter this text to unzip the package …

tar jxvf AIR20_mac_sdk_*****.tbz2

… replace the asterisks with the version number of the beta you downloaded. You’ll have something like this in your terminal window :

Terminal 2014 bash 2014 80ղ4
Uploaded with plasq‘s Skitch!

Then hit return and you should see all the files being installed into the appropriate folders.

Now open Flash Builder 4 and go to the preferences to change the SDK. Choose the new SDK folder you duplicated which has now got the new AIR 2 packages installed.

Preferences
Uploaded with plasq‘s Skitch!

Now create a new Flex AIR project and go to the project preferences to choose your new compiler SDK:

Properties for MultiTouchTest_html
Uploaded with plasq‘s Skitch!

Now you are ready to build a new AIR 2 project with cool multi-touch events! And here’s one I made earlier which demos four of the new TransformGestureEvent Events that you can play with just using the MacBook multi-touch trackpad. It’s basically a small little app of a Monkey that you can scale with a pinch and rotate with a two finger twist. You can also drag him around with a two-finger pan and also make him spring up, down, left and right by swiping on the track pad with three fingers.

Let’s jump straight into the code. I’ve built this in Flex so I use MXML to generate my application window and add a simple Sprite object to the stage using SpriteVisualElement. There’s a simple SWC that contains a Sprite that I created in Flash and exported as ‘Monkey’. This can then be added to the SpriteVisualElement object as a child.

Here’s the full Application class :

View the full source and download the project here.

Grab the AIR file and play on your desktop here. You’ll need the AIR 2 runtime that you can grab here.

If you want to install your tests on an iPhone that’s a blog post for the near future … you can’t do it from within FB4 at the moment and I aint got CS5. My attempts at doing it from the command line failed but I think that’s because it doesn’t like the SWF that FB4 produced. Will look into it.

Hope you enjoy touching my monkey you dirty boys and girls!

WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS Log in