protected var _htmlButtons:Array;
protected var _htmlButtonLinks:Array;
/**
* Create hit areas for html links - since they aren't handled automatically by PV
*/
protected function activateHrefs( textField:TextField ):void
{
var htmlTxtStr:String = textField.htmlText;
var plainTxtStr:String = textField.text;
var linkOpens:Array = getIndexesOfArray( htmlTxtStr, "<a " );
var linkCloses:Array = getIndexesOfArray( htmlTxtStr, "</a>" );
_htmlButtons = new Array();
_htmlButtonLinks = new Array();
// helps step through and not repeat duplicate links
var lastPlanTextIndex:int = 0;
// loop through links found
for( var i:int = 0; i < linkOpens.length; i++ )
{
// create button
var button:Sprite = new Sprite();
button.x = textField.x;
button.y = textField.y;
this.addChild( button );
// get text position in html text
var firstCharIndex:int = linkOpens[i];
var linkLength:int = linkCloses[i] - linkOpens[i] + 4;
// pull out string inside open and close tags
var linkString:String = htmlTxtStr.substr( firstCharIndex, linkLength );
// get href from <a> tag
var hrefPattern:RegExp = /href=['"]\S+['"]/i;
var hrefs:Array = linkString.match( hrefPattern );
var href:String = ( hrefs ) ? hrefs[0].substring(6, hrefs[0].length - 1) : "";
// strip tags
linkString = linkString.substr( linkString.indexOf( ">" ) + 1 ); // chop open tag
linkString = linkString.substr( 0, linkString.length - 4 ); // chop end tag
// find link text in non-html text
var linkStringPlainTextIndex:int = plainTxtStr.indexOf( linkString, lastPlanTextIndex );
lastPlanTextIndex = linkStringPlainTextIndex;
// draw rects for letters
button.graphics.beginFill(0xFF0000, 0);
for( var j:int = linkStringPlainTextIndex; j < linkStringPlainTextIndex + linkString.length; j++ )
{
var charRect:Rectangle = textField.getCharBoundaries(j);
if( charRect ) button.graphics.drawRect(charRect.x, charRect.y, charRect.width, charRect.height);
}
button.graphics.endFill();
// add listeners
button.addEventListener( MouseEvent.CLICK, onHyperlinkClick );
button.addEventListener( MouseEvent.MOUSE_OVER, onHtmlLinkOver );
button.addEventListener( MouseEvent.MOUSE_OUT, onHtmlLinkOut );
// store button and link so we can launch on click
_htmlButtons.push( button );
_htmlButtonLinks.push( href );
}
}
/**
* Returns an array of all the indexes of needle in haystack
*/
protected function getIndexesOfArray( haystack:String, needle:String ) : Array
{
var indexs:Array = new Array();
var startIndex:int = 0;
while( startIndex != -1 )
{
startIndex = haystack.indexOf( needle, startIndex );
if( startIndex != -1 )
{
indexs.push( startIndex );
startIndex += 1;
}
}
return indexs;
}
/**
* simply opens the link
*/
protected function onHyperlinkClick( e:MouseEvent ) : void
{
// find button and launch corresponding link
for( var i:int = 0; i < _htmlButtons.length; i++ )
{
if( e.target == _htmlButtons[i] )
{
navigateToURL( new URLRequest( _htmlButtonLinks[i] ), '_blank' );
}
}
}
protected function onHtmlLinkOver( e:MouseEvent ):void
{
// dispatch an Event to tell the PV3D viewport to enable the hand cursor:
// ( _pvView as BasicView).viewport.buttonMode = true;
}
protected function onHtmlLinkOut( e:MouseEvent ):void
{
// dispatch an Event to tell the PV3D viewport to disable the hand cursor:
// ( _pvView as BasicView).viewport.buttonMode = false;
}
/**
* clean up when if leave the papervision section
*/
public function dispose():void
{
// kill html hyperlink buttons
if(_htmlButtons != null) {
for( var i:int = 0; i < _htmlButtons.length; i++ )
{
_htmlButtons[i].removeEventListener( MouseEvent.CLICK, onHyperlinkClick );
_htmlButtons[i].removeEventListener( MouseEvent.MOUSE_OVER, onHtmlLinkOver );
_htmlButtons[i].removeEventListener( MouseEvent.MOUSE_OUT, onHtmlLinkOut );
}
_htmlButtons.splice( 0 );
_htmlButtonLinks.splice(0);
_htmlButtons = null;
_htmlButtonLinks = null;
}
}
A collection of handy code snippets in the languages that I use on a daily basis.
September 2, 2009
ActionScript 3: Papervision3d: html links in a TextField
Papervision is fun, and the most commonly used 3D library in Flash. The developers have done an amazing job taking care of so many issues that arise when rendering multiple interactive objects to one BitmapData object. For example, if you create a 2D Sprite, put a button inside it somewhere, and then use that Sprite as a MovieMaterial texture for a 3D object, the MouseEvent listeners are automatically handled, and your buttons in the texture work as expected, which is awesome. One major issue along these lines is that if you have an html-enabled TextField with active hyperlinks, this button functionality does not get forwarded through the Papervision core. I saw some code that another developer had posted on a forum, and I rewrote it to work nicely with multiple links. The idea is that you can find the character positions of a hyperlink's text in the TextField, and draw buttons as Sprites in the 2D texture, on top of the TextField, so that your hyperlinks will have an active hit state and work inside a 3D object. I also included a dispose function to clean up, and have rollover listeners to enable the hand cursor in the PV3D viewport, which happens in a different class. This all could've been done a little cleaner with multiple classes and more regular expressions, but I wanted to keep it really simple and easy to implement and garbage collect. Here's the code:
Labels:
actionscript 3,
as3,
click,
html,
hyperlink,
link,
papervision,
papervision3d,
textfield
September 1, 2009
ActionScript 3: Choose a random item from an Array, with weighting
It's very common to simply choose a random item from an Array, using a random number. But what if we want change the probabilities that certain items will be chosen? I've written a little function that takes an Array of weights (Numbers), which correspond to the Array of items you'd like to choose from, and returns an index to pull a random, weighted item from the source Array. Check out the example:
You can see that the weights array must be the same length as the data array that you're choosing from. Note that in the example, my weights add up to 100, but you can use any scale that you'd like, as the weights are added up, and a random number is chosen in that scale.
// our array of items
var fruits:Array = ['apple','orange','banana','mango'];
// our array of weights
var weights:Array = [20,10,40,30];
// pick a random fruit, based on weights, with bananas most likely to get picked
var myFruit:String = fruits[ randomIndexByWeights( weights ) ];
/**
* Takes an array of weights, and returns a random index based on the weights
*/
private function randomIndexByWeights( weights:Array ) : int
{
// add weights
var weightsTotal:Number = 0;
for( var i:int = 0; i < weights.length; i++ ) weightsTotal += weights[i];
// pick a random number in the total range
var rand:Number = Math.random() * weightsTotal;
// step through array to find where that would be
weightsTotal = 0;
for( i = 0; i < weights.length; i++ )
{
weightsTotal += weights[i];
if( rand < weightsTotal ) return i;
}
// if random num is exactly = weightsTotal
return weights.length - 1;
}
You can see that the weights array must be the same length as the data array that you're choosing from. Note that in the example, my weights add up to 100, but you can use any scale that you'd like, as the weights are added up, and a random number is chosen in that scale.
Labels:
actionscript 3,
array,
random number,
weighted
ActionScript 3: Shuffle/randomize any Array
A quick little function for randomizing/shuffling an Array that contains objects or primitive of any data type:
You can see that it crawls through an Array, swapping each position with another, random position in the Array.
public static function randomizeArray( arr:Array ):void
{
for( var i:int = 0; i < arr.length; i++ )
{
var tmp:* = arr[i];
var randomIndex:int = Math.round( Math.random() * ( arr.length - 1 ) );
arr[i] = arr[randomIndex];
arr[randomIndex] = tmp;
}
}
You can see that it crawls through an Array, swapping each position with another, random position in the Array.
Labels:
actionscript 3,
array,
as3,
randomize,
shuffle
ActionScript 3: Adding a textual suffix to numbers
If you need to write out a number with a textual suffix, like "25th" or "173rd", there's a little logic that will make this really easy. Check it out:
// suffixes corresponding to the last digit of a number: 0-9
private static const NUMBER_SUFFIXES:Array = [ 'th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th' ];
private function getNumberSuffix( value : int ) : String
{
// handle most cases by modding by ten
var suffix:String = NUMBER_SUFFIXES[ value % 10 ];
if( value % 100 >= 11 && value % 100 <= 13 ) suffix = 'th'; // handle 11-13
if( value == 0 ) suffix = ''; // handle zero
return suffix;
}
Labels:
random number,
string,
suffix,
textual
August 31, 2009
ActionScript 3: Get the percentage of a value within a number range
Sometimes, when creating UI elements or performing geometric calculations based on the sizes or positions of objects (or lots of other tasks), it's useful to find the percentage of a number within a range of 2 other numbers. Use this function to easily relate these values:
/**
* Returns a percentage of a value in between 2 other numbers.
* @param bottomRange low end of the range.
* @param topRange top end of the range.
* @param valueInRange value to find a range percentage of.
* @return The percentage of valueInRange in the range.
* @use getPercentWithinRange( 50, 150, 100 ); // displays 50
*/
public static function getPercentWithinRange( bottomRange:Number, topRange:Number, valueInRange:Number ):Number
{
// normalize values to work off zero
if( bottomRange < 0 )
{
var addToAll:Number = Math.abs( bottomRange );
bottomRange += addToAll;
topRange += addToAll;
valueInRange += addToAll;
}
else if( bottomRange > 0 )
{
var subFromAll:Number = Math.abs( bottomRange );
bottomRange -= subFromAll;
topRange -= subFromAll;
valueInRange -= subFromAll;
}
// simple calc to get percentage
return 100 * ( valueInRange / ( topRange - bottomRange ) );
}
Labels:
percentage,
random number,
value
ActionScript 3: Constant easing function
Sometimes you want to ease a numeric variable or object property to a new value. Typically, you would do this with a tween, via the built-in Tween object, or a 3rd party tween library like Tweener or TweenLite/TweenMax. Sometimes, for a fluid interface, you might want different properties constantly easing (floating) towards their current target destination. In this situation, you would want to interpolate the values incrementally towards the target using the Event.ENTER_FRAME listener. I wrote a nice little function that will take the current value, the target value, and an optional easing value (where higher numbers increase the time to reach the target value), and ease the property towards the target. Check it out:
Now, wherever mySprite is, it will smoothly tween towards the x destination of 200. This can be applied to any numeric property.
private function easeTo( current:Number, target:Number, easeFactor:Number = 10 ):Number
{
return current -= ( ( current - target ) / easeFactor );
}
// usage:
this.addEventListener( Event.ENTER_FRAME, onEnterFrameLoop );
private function onEnterFrameLoop( e:Event ):void {
mySprite.x = easeTo( mySprite.x, 200, 5 );
}
Now, wherever mySprite is, it will smoothly tween towards the x destination of 200. This can be applied to any numeric property.
June 18, 2009
Actionscript: Crossdomain issue when loading Twitter user icons
When building a new Flash piece, I ran into a crossdomain issue when trying to load the user icon images that come back from the Twitter API. After some searching, I found that by replacing the root of the url, the crossdomain issues are easily solved. Check it out:
I was surprised it was this easy, but by moving the "twitter_production" portion of the path to the front of the subdomain, you now get a good crossdomain.xml here: http://twitter_production.s3.amazonaws.com/crossdomain.xml
var thumbnail:String = "http://s3.amazonaws.com/twitter_production/profile_images/252274568/profile_image_normal.jpg";
thumbnail = thumbnail.replace(/http:\/\/s3.amazonaws.com\/twitter_production\//g, 'http://twitter_production.s3.amazonaws.com/');
I was surprised it was this easy, but by moving the "twitter_production" portion of the path to the front of the subdomain, you now get a good crossdomain.xml here: http://twitter_production.s3.amazonaws.com/crossdomain.xml
Labels:
actionscript,
amazonaws,
crossdomain.xml,
flash,
twitter
May 13, 2009
Processing: Spiral growth generative art
A coworker got me excited to play around with Processing again, and I wrote a couple new generative art scripts. Here's a fun one:
It ends up looking something like this:
float x;
float y;
float curRotation;
float rotationVelocity;
float curRadius;
float radiusVelocity;
float radiusThreshold;
float curSize;
float sizeVelocity;
float curRed;
float redVelocity;
float curGreen;
float greenVelocity;
float curBlue;
float blueVelocity;
void setup(){
size(1000,1000);
smooth();
background(45,0,0);
frameRate(100);
reset();
}
void draw(){
x = (width / 2) + sin(curRotation) * curRadius;
y = (height / 2) + cos(curRotation) * curRadius;
noStroke();
//stroke(100,100,100,150);
fill(curRed,curGreen,curBlue);
ellipse(x,y,curSize,curSize);
curRadius += radiusVelocity;
curRotation += rotationVelocity;
curSize -= sizeVelocity;
if( curRed > 45 ) curRed -= redVelocity;
curGreen -= greenVelocity;
curBlue -= blueVelocity;
if( curSize <= 0 ) //curRadius > radiusThreshold ||
{
reset();
}
}
void reset(){
curRadius = 0;
curRotation = random(0,3.14*2);
curSize = 20;
sizeVelocity = random(.05, .2);
rotationVelocity = random(.001, .009);
radiusVelocity = random( .1, 2 );
radiusThreshold = random( width / 3, width / 4 );
// make a shade of red
curRed = random(100, 255);
redVelocity = random( .1, 1 );
curGreen = random(0, 50);
greenVelocity = .5 * redVelocity;
curBlue = curGreen;
blueVelocity = greenVelocity;
}
It ends up looking something like this:
Labels:
generative art,
processing,
processing.org
April 22, 2009
ActionScript 3: 502 error on crossdomain.xml request in IE
My team and I ran into a very strange security error today while testing our new Flash projects that load assets from external URLs. It only showed up in IE, which was quite mystifying. We had taken care of all of our crossdomain issues by updating our crossdomain.xml files, adding proper security code to all of our media loads as such:
But something wasn't right. I installed Fiddler, which is a super handy tool for debugging in IE, since FireBug is only available for FireFox. We found that IE was making the request for crossdomain.xml, but the server it was requesting from was the name of the media file, and not the actual server. After a bit of poking around we found that the CMS we were using was automatically formatting the Flash embed code, and was adding this:
This simple embed param caused the super weird behavior, only in IE. Hopefully this may be helpful to someone who runs into the same issue. Yay IE!
new LoaderContext( true, ApplicationDomain.currentDomain, SecurityDomain.currentDomain );
But something wasn't right. I installed Fiddler, which is a super handy tool for debugging in IE, since FireBug is only available for FireFox. We found that IE was making the request for crossdomain.xml, but the server it was requesting from was the name of the media file, and not the actual server. After a bit of poking around we found that the CMS we were using was automatically formatting the Flash embed code, and was adding this:
<param value="" name="base">
This simple embed param caused the super weird behavior, only in IE. Hopefully this may be helpful to someone who runs into the same issue. Yay IE!
Labels:
502 error,
as3,
base,
crossdomain.xml,
security
April 21, 2009
Javascript: scroll / animate a background image
I got the urge to add a little flavor to a repeating background image on one of my personal sites. When you roll over the div with your mouse, the image starts scrolling by incrementing the background-position css. First you need a div with a background-image, a background-position, and an id of "header":
Then you need to attach the mouse rollover functionality to the header - I did this dynamically with protoculous, as seen below in the final Event.observe method. The rest is simple: start a timer, grab the background position offset from the css, increment, and apply the reassembled css. Easy:
#header {
background-image:url(../images/site-header-single.gif);
background-position:-73px 0;
background-repeat:repeat-x;
width:752px;
height:242px;
}
Then you need to attach the mouse rollover functionality to the header - I did this dynamically with protoculous, as seen below in the final Event.observe method. The rest is simple: start a timer, grab the background position offset from the css, increment, and apply the reassembled css. Easy:
// setTimeout variable for clearing on rollout
var headerTimeout;
function addHeaderBehavior() {
Event.observe( $('header'), 'mouseover', startHeaderAnim, false);
Event.observe( $('header'), 'mouseout', stopHeaderAnim, false);
}
function startHeaderAnim() {
headerTimeout = setTimeout( "incrementHeader()", 50 );
}
function incrementHeader()
{
// get x variable of background-position
var bgOffsets = $('header').getStyle('background-position').split(' ');
var offsetX = bgOffsets[0];
var offsetY = bgOffsets[1];
// strip px and increment
offsetX = offsetX.replace( /px/, '' );
offsetX = parseInt( offsetX ) + 1;
// update style
$('header').setStyle( { backgroundPosition:offsetX + 'px ' + offsetY } );
startHeaderAnim();
}
function stopHeaderAnim(event) {
clearTimeout( headerTimeout );
}
Event.observe(window, 'load', addHeaderBehavior, false);
Labels:
animate,
css,
javascript,
settimeout
April 2, 2009
ActionScript 3: Easily generate code to draw a complex image
In Flash, I always do everything in a programmatic way if possible. This includes drawing shapes with code instead of having library items. This is especially true, now that the preferred method of compiling is using the Flex mxmlc compiler. Sometimes, though, certain things are too much of a pain to draw pixel by pixel. No longer though, as there's a REALLY simple way to generate the code to draw complex shapes for you.
Step 1: save a .png of the image you want drawn.
Step 2: open Flash, and drop your image into the library, giving it a linkage ID of "MyGraphic".
Step 3: drop the following code into the actions panel and publish.
Step 4: copy the output code and apply it to a BitmapData object in your project.
the resulting code in my case looked like this:
Notes: using the setPixel32() function, and defaulting the BitmapData to a background color of 0x00000000 ensures that your Bitmap will have a transparent background. Note that the code here ignores empty pixels for efficiency. Finally, this code could easily be converted to draw into a Shape or Sprite, but for my purposes, bitmaps are usually what I want. Hope you find this useful!
Step 1: save a .png of the image you want drawn.
Step 2: open Flash, and drop your image into the library, giving it a linkage ID of "MyGraphic".
Step 3: drop the following code into the actions panel and publish.
Step 4: copy the output code and apply it to a BitmapData object in your project.
var mc : MyGraphic = new MyGraphic( null, null );
for( var i:int = 0; i < mc.width; i ++)
{
for( var j:int = 0; j < mc.height; j ++)
{
if( mc.getPixel32( i, j ) != 0 ) trace( "bmp.setPixel32( "+i+", "+j+", "+mc.getPixel32( i, j )+" );" );
}
}
the resulting code in my case looked like this:
public static function drawVideoPlayhead():Bitmap
{
var bitmap:Bitmap = new Bitmap();
var bmp:BitmapData = new BitmapData( 7, 8, true, 0x00000000 );
// begin generated code
bmp.setPixel32( 0, 3, 4289309097 );
bmp.setPixel32( 0, 4, 4288322202 );
bmp.setPixel32( 0, 5, 4287664272 );
bmp.setPixel32( 0, 6, 4286874756 );
bmp.setPixel32( 1, 2, 4290888129 );
bmp.setPixel32( 1, 3, 4291085508 );
bmp.setPixel32( 1, 4, 4290822336 );
bmp.setPixel32( 1, 5, 4290032820 );
bmp.setPixel32( 1, 6, 4288256409 );
bmp.setPixel32( 1, 7, 4286874756 );
bmp.setPixel32( 2, 1, 4291677645 );
bmp.setPixel32( 2, 2, 4291875024 );
bmp.setPixel32( 2, 3, 4291677645 );
bmp.setPixel32( 2, 4, 4290822336 );
bmp.setPixel32( 2, 5, 4290032820 );
bmp.setPixel32( 2, 6, 4289374890 );
bmp.setPixel32( 2, 7, 4287598479 );
bmp.setPixel32( 3, 0, 4293322470 );
bmp.setPixel32( 3, 1, 4293388263 );
bmp.setPixel32( 3, 2, 4292335575 );
bmp.setPixel32( 3, 3, 4291677645 );
bmp.setPixel32( 3, 4, 4290822336 );
bmp.setPixel32( 3, 5, 4290032820 );
bmp.setPixel32( 3, 6, 4289374890 );
bmp.setPixel32( 3, 7, 4287598479 );
bmp.setPixel32( 4, 1, 4294046193 );
bmp.setPixel32( 4, 2, 4293256677 );
bmp.setPixel32( 4, 3, 4291677645 );
bmp.setPixel32( 4, 4, 4290822336 );
bmp.setPixel32( 4, 5, 4290032820 );
bmp.setPixel32( 4, 6, 4289374890 );
bmp.setPixel32( 4, 7, 4287598479 );
bmp.setPixel32( 5, 2, 4293717228 );
bmp.setPixel32( 5, 3, 4292861919 );
bmp.setPixel32( 5, 4, 4290822336 );
bmp.setPixel32( 5, 5, 4290032820 );
bmp.setPixel32( 5, 6, 4289703855 );
bmp.setPixel32( 5, 7, 4288059030 );
bmp.setPixel32( 6, 3, 4293914607 );
bmp.setPixel32( 6, 4, 4293190884 );
bmp.setPixel32( 6, 5, 4292730333 );
bmp.setPixel32( 6, 6, 4291677645 );
// end generated code
bitmap.bitmapData = bmp;
return bitmap;
}
Notes: using the setPixel32() function, and defaulting the BitmapData to a background color of 0x00000000 ensures that your Bitmap will have a transparent background. Note that the code here ignores empty pixels for efficiency. Finally, this code could easily be converted to draw into a Shape or Sprite, but for my purposes, bitmaps are usually what I want. Hope you find this useful!
March 16, 2009
ActionScript 3: Toggling a Boolean the easy way
This may be common knowledge to some, but I was pretty excited when I realized I didn't need an conditional statement to figure out which direction to toggle a Boolean value. Check it out:
Simplicity is your friend.
var isTrue:Boolean = true;
isTrue = !isTrue;
trace(isTrue); // prints "false"
isTrue = !isTrue;
trace(isTrue); // prints "true"
Simplicity is your friend.
Labels:
actionscript 3,
as3,
boolean,
toggle
March 9, 2009
ActionScript 3: Checking for empty xml nodes
When parsing xml in AS3, you grab a leaf node's inner content by using the XML( node ).text() function. You can compare the result with a String, but if you're checking for an empty string, the .text() function doesn't compute, because it returns an XMLList, instead of a String. While a non-empty string comparison works fine between an XMLList and a String, an empty String ( i.e. var mystring:String = ""; ) is not the same as an empty XMLList. To ensure that your comparison works, it's always a good policy to cast your text node to a String. See the example below:
var isEmptyNode:Boolean = false;
if( String( node["myNodeName"].text() ) == "" )
{
isEmptyNode = true;
}
February 4, 2009
iPhone: MPMoviePlayerController preload issues
As of this writing, the MPMoviePlayerController object doesn't seem to send the MPMoviePlayerContentPreloadDidFinishNotification notification on the iPhone Simulator. This is happening when I'm loading an .m4v over the net. This issue makes my app appear broken on the simulator, because I'm waiting for the preload to finish before calling the "play" function on the MPMoviePlayerController object. When I test on the iPhone itself, there's no problem. Just an FYI.
Labels:
cocoa,
iphone,
m4v,
MPMoviePlayerController,
objective-c,
preload,
simulator
January 30, 2009
iPhone: Rotate a UIImageView
Here's a simple little snippet that's useful if you want to rotate a UIImageView continuously for something like a clock hand graphic:
#define DEGREES_TO_RADIANS(__ANGLE__) ((__ANGLE__) / 180.0 * M_PI)
CGAffineTransform cgCTM = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(45));
myImage.transform = cgCTM;
Labels:
cocoa,
iphone,
objective-c,
rotate,
uiimageview
iPhone: Custom font loading : complete example
UPDATE: It looks like someone has built a far more robust custom font engine than my example here. I haven't tried it yet, but the example project looks great: http://github.com/zynga/FontLabel
This code was cobbled together from a couple different posts I found on blogs and iphone forums, here and here. This is a complete example, which nobody wanted to supply - I hope this is useful to someone. The one major hole is that is does not support multi-line text. Let me know if you figure out a good way to handle it :)
I'm just going to post my 2 classes here. I've set up a base class that does the drawing, and a subclass that defines font-specific configuration. You'll have to put your .ttf (TrueType) font in the bundle, and use it's name there to customize your own font subclass. You'll also have to set the glyph offset in the subclass - a font-editing tool can help with this.
Here we go:
CustomFontBase.h
CustomFontBase.m
This code was cobbled together from a couple different posts I found on blogs and iphone forums, here and here. This is a complete example, which nobody wanted to supply - I hope this is useful to someone. The one major hole is that is does not support multi-line text. Let me know if you figure out a good way to handle it :)
I'm just going to post my 2 classes here. I've set up a base class that does the drawing, and a subclass that defines font-specific configuration. You'll have to put your .ttf (TrueType) font in the bundle, and use it's name there to customize your own font subclass. You'll also have to set the glyph offset in the subclass - a font-editing tool can help with this.
Here we go:
CustomFontBase.h
@interface CustomFontBase : UIView { NSMutableString *curText; UIColor *fontColor; UIColor *bgColor; int fontSize; NSString *fontName; NSString *fontExtension; float autoSizeWidth; int glyphOffset; BOOL isGlowing; UIColor *glowColor; } - (void)updateText:(NSString*)newText; - (void)initTextWithSize:(float)size color:(UIColor*)color bgColor:(UIColor*)bgColor; - (void)setGlow:(BOOL)glowing withColor:(UIColor*)color; - (void)autoSizeWidthNow; @property (nonatomic, retain) NSMutableString *curText; @end
CustomFontBase.m
#import "CustomFontBase.h" @implementation CustomFontBase @synthesize curText; - (id)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { // set defaults [self setBackgroundColor:[UIColor clearColor]]; bgColor = [UIColor clearColor]; [self setCurText: [[NSMutableString alloc] initWithString:@""] ]; fontColor = [UIColor whiteColor]; fontSize = 15; isGlowing = FALSE; [self setContentMode:UIViewContentModeTopLeft]; // make sure it doesn't scale/deform when setFrame is called } return self; } - (void)drawRect:(CGRect)rect { // get context and flip for normal coordinates CGContextRef context = UIGraphicsGetCurrentContext(); CGContextTranslateCTM ( context, 0, self.bounds.size.height ); CGContextScaleCTM ( context, 1.0, -1.0 ); // Get the path to our custom font and create a data provider. NSString *fontPath = [[NSBundle mainBundle] pathForResource:fontName ofType:fontExtension]; CGDataProviderRef fontDataProvider = CGDataProviderCreateWithFilename([fontPath UTF8String]); // Create the font with the data provider, then release the data provider. CGFontRef customFont = CGFontCreateWithDataProvider(fontDataProvider); CGDataProviderRelease(fontDataProvider); // Set the customFont to be the font used to draw. CGContextSetFont(context, customFont); // prepare characters for printing NSString *theText = [NSString stringWithString: curText]; int length = [theText length]; unichar chars[length]; CGGlyph glyphs[length]; [theText getCharacters:chars range:NSMakeRange(0, length)]; // draw bg if( bgColor != [UIColor clearColor] ) { CGRect bgRect = CGRectMake (0, 0, self.bounds.size.width, self.bounds.size.height); CGContextSetFillColorWithColor( context, bgColor.CGColor ); CGContextFillRect( context, bgRect ); } // Set how the context draws the font, what color, how big. CGContextSetTextDrawingMode(context, kCGTextFill); CGContextSetFillColorWithColor(context, fontColor.CGColor ); CGContextSetFontSize(context, fontSize); // set a glow? if( isGlowing ) { //CGContextSetShadow(context, CGSizeMake(0,0), 3 ); CGContextSetShadowWithColor( context, CGSizeMake(0,0), 3, glowColor.CGColor ); } // Loop through the entire length of the text. for (int i = 0; i < length; ++i) { // Store each letter in a Glyph and subtract the MagicNumber to get appropriate value. glyphs[i] = [theText characterAtIndex:i] + glyphOffset; } // draw the glyphs CGContextShowGlyphsAtPoint( context, 0, 0 + fontSize * .25, glyphs, length ); // hack the y-point to make sure it's not cut off below font baseline - this creates a perfect vertical fit // get width of text for autosizing the frame later (perhaps) CGPoint textEnd = CGContextGetTextPosition( context ); autoSizeWidth = textEnd.x; // clean up the font CGFontRelease( customFont ); } // call this after creating the LandscapeText object to set the styling - (void)initTextWithSize:(float)size color:(UIColor*)color bgColor:(UIColor*)txtBgColor { // store font properties fontColor = color; fontSize = size; bgColor = txtBgColor; // autoscale height to font size [self setFrame:CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, fontSize)]; } // set new text to display - (void)updateText:(NSString*)newText { [self setCurText: [NSString stringWithString:newText] ]; [self setNeedsDisplay]; } - (void)setGlow:(BOOL)glowing withColor:(UIColor*)color { glowColor = color; isGlowing = glowing; } - (void)autoSizeWidthNow { //printf( "autoSizeWidth = %f \n", autoSizeWidth ); [self setFrame:CGRectMake(self.frame.origin.x, self.frame.origin.y, autoSizeWidth, fontSize)]; } - (void)dealloc { [curText release]; [super dealloc]; } @endCustomFontMyFont.h
#importCustomFontMyFont.m#import "CustomFontBase.h" @interface CustomFontMyFont : CustomFontBase { } @end
#import "CustomFontMyFont.h" @implementation CustomFontMyFont - (id)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { // Initialization code fontName = @"MyFont"; fontExtension = @"ttf"; glyphOffset = -29; // adjust this offset per font until it prints the proper characters } return self; } - (void)dealloc { [super dealloc]; } @endUsage:
CustomFontMyFont *myLabel = [[[CustomFontMyFont alloc] initWithFrame:CGRectMake(100, 100, 55, 20)] autorelease]; [myLabel initTextWithSize:11 color:[UIColor whiteColor] bgColor:[UIColor clearColor]]; [myLabel updateText:@"Custom Font"]; [self addSubview:myLabel];
Labels:
cocoa,
custom font,
iphone,
objective-c,
truetype,
ttf
January 29, 2009
iPhone: Attach an image with one line of code
I got sick of the 4 lines of code it takes to attach and garbage collect a UIImageView to your UIView, so I wrote a static function for our DisplayUtil class to clean up this process. Check it out below and feel free to use:
+ (UIImageView*)attachImageToView:(UIView*)viewObject withId:(NSString*)imgId andRect:(CGRect)rect {
UIImageView *img = [[UIImageView alloc] initWithFrame:rect];
img.image = [UIImage imageNamed:imgId];
[viewObject addSubview:img];
[img release];
return img;
}
Labels:
addsubview,
attach,
cocoa,
function,
objective-c,
static,
uiimageview,
uiview
January 27, 2009
ActionScript 3: Prevent graphics from blocking your button
In Flash 9, a non-button graphic that lays over a real button wil prevent the user from clicking the button underneath. This was never a problem in Flash before AS3 / Flash 9, but is quite a common problem to deal with. It's quite easy to get around the issue. You simply need to disable any mouse interaction with the inactive graphic. Even though you haven't told it to act as a button, the default is to capture mouse actions as a DisplayObject. Just add this code to the non-button graphic:
Fixed!
graphic.mouseChildren = false;
graphic.mouseEnabled = false;
Fixed!
Labels:
actionscript 3,
as3,
block,
button,
click,
displayobject,
graphic
January 21, 2009
iPhone: Disable screen dimming
This can be very handy if you're developing an iPhone game, where the screen shouldn't automatically dim to save battery life. Use this code when you launch your gameplay view, and set it back when you leave gameplay:
[[UIApplication sharedApplication] setIdleTimerDisabled:YES];
January 19, 2009
iPhone: send and receive events/notifications
Objective-C has a really friendly and useful event system that allows your objects to talk to each other without any knowledge of the other objects. Events are called Notifications in Objective-C, and are routed through a system-wide object called NSNotificationCenter. You don't need to import this object - it's always available to every class.
Here's the syntax for dispatching an event/notification with an ID of "EventName":
It also sends along "self", which is a reference to the dispatching object. You could replace "self" with a reference to another object that may contain more relevant data to the event being dispatched.
To respond to this event, you need to set up a listener:
Our object, "self" is now ready to receive this event. It must have a function called "listenerFunction", but this name is up to you. Note that setting "nil" as the object lets us respond to any object in the application that sends out a notification called "EventName". Alternatively, we can listen to only one object that will send this event, by replacing "nil" with a reference to the object.
Finally, you need a function that actually does something when the event/notification is received. See below:
If we don't need to do anything with the object that was sent with the event, we can ignore the first line in the function.
[UPDATE]: Thanks to CC in the comment section for correcting my example and reminding us that we want to clean up after ourselves and remove event listeners before things are properly garbage collected. Here's the syntax for removing the listener we added above (replace "nil" with the object reference if you were listening to a specific object):
Finally, here's Apple's official Notification documentation for further reading:
http://developer.apple.com/documentation/Cocoa/Conceptual/Notifications/Introduction/introNotifications.html
Here's the syntax for dispatching an event/notification with an ID of "EventName":
[[NSNotificationCenter defaultCenter] postNotificationName:"EventName" object:self];
It also sends along "self", which is a reference to the dispatching object. You could replace "self" with a reference to another object that may contain more relevant data to the event being dispatched.
To respond to this event, you need to set up a listener:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(listenerFunction:) name:@"EventName" object:nil];
Our object, "self" is now ready to receive this event. It must have a function called "listenerFunction", but this name is up to you. Note that setting "nil" as the object lets us respond to any object in the application that sends out a notification called "EventName". Alternatively, we can listen to only one object that will send this event, by replacing "nil" with a reference to the object.
Finally, you need a function that actually does something when the event/notification is received. See below:
- (void)listenerFunction:(NSNotification *)notification
{
MyDispactherObject *incomingObject = [notification object];
NSLog(@"EVENT RECEIVED");
}
If we don't need to do anything with the object that was sent with the event, we can ignore the first line in the function.
[UPDATE]: Thanks to CC in the comment section for correcting my example and reminding us that we want to clean up after ourselves and remove event listeners before things are properly garbage collected. Here's the syntax for removing the listener we added above (replace "nil" with the object reference if you were listening to a specific object):
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"EventName" object:nil];
Finally, here's Apple's official Notification documentation for further reading:
http://developer.apple.com/documentation/Cocoa/Conceptual/Notifications/Introduction/introNotifications.html
Labels:
cocoa,
event,
iphone,
notification,
nsnotification,
objective-c
January 16, 2009
ActionScript: Random number function
I saw that one of the top results for "actionscript random number" in a Google search is incorrect, so I figured it would be a good idea to post another proper one.
Here's the ActionScript 3 version:
And here's the ActionScript 2 version:
Here's the ActionScript 3 version:
function randNum( low:int, high:int ):int
{
return Math.round( Math.random() * (high - low) ) + low;
}
And here's the ActionScript 2 version:
function randNum( low:Number, high:Number ):Number
{
return Math.round( Math.random() * (high - low) ) + low;
}
Labels:
actionscript,
actionscript 2,
actionscript 3,
as2,
as3,
function,
random number
January 15, 2009
Javascript: setInterval() - call a function every X seconds
setInterval() is used when you want a function to run repeatedly on a timer. I wrote this little html page to remind me to get up and stretch every half-hour. I keep it open in my browser during the day to keep me from slouching for too long. Programming for hours on end can lead to Repetitive Strain Injuries in the hands and arms (amongst other problems), so this could help you too :)
Note the time passed into the setInterval function. It means: 1000 milliseconds x 60 seconds x 30 minutes. setInterval uses milliseconds, so this is a clear way to describe how long you want the interval to be.
<html>
<head>
<script>
setInterval ( "tellMeToGetUp()", 1000 * 60 * 30 );
function tellMeToGetUp()
{
alert('GET UP DUDE!');
}
</script>
</head>
<body>
</body>
</html>
Labels:
function,
javascript,
js,
setinterval,
timer
January 14, 2009
iPhone: Rotate a sub view to landscape layout
After trying some other hacks, I came up with this little bit of code to rotate my full-screen UIView to landscape mode, which let me lay out all the sub views in a standard coordinate system.
#define DEGREES_TO_RADIANS(__ANGLE__) ((__ANGLE__) / 180.0 * M_PI)
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
self.backgroundColor = [UIColor clearColor];
[self setViewToLandscape:self];
}
return self;
}
-(void)setViewToLandscape:(UIView*)viewObject {
[viewObject setCenter:CGPointMake(160, 240)];
CGAffineTransform cgCTM = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(-90));
viewObject.transform = cgCTM;
viewObject.bounds = CGRectMake(0, 0, 480, 320);
}
January 13, 2009
iPhone: Create a UIButton with a custom graphic
Here's a little code snippet if you don't want to use the standard UIButton styles. You can attach an image instead:
UIButton *button = [[UIButton buttonWithType:UIButtonTypeCustom] initWithFrame:CGRectMake(0, 0, 24, 24)];
[button addTarget:self action:@selector(prevButtonClick:) forControlEvents:UIControlEventTouchUpInside];
[button setBackgroundImage:[UIImage imageNamed:@"IntroArrowLeft.png"] forState:UIControlStateNormal];
[self addSubview:button];
January 12, 2009
iPhone: Create a UIButton and respond to the touch
Here's the basic code to create a text button and do something when it's touched:
// add the button to the view
-(void)buildButton {
UIButton *button = [[UIButton buttonWithType:UIButtonTypeRoundedRect] initWithFrame:CGRectMake(0, 0, 150, 25)];
[button addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:@"CLICK IT" forState:UIControlStateNormal];
[button setCenter:CGPointMake( 320 / 2, 480 - 25 )];
[self addSubview:button];
}
// respond to the button click
-(void)buttonClick:(UIView*)clickedButton
{
NSLog(@"CLICK!");
}
Subscribe to:
Posts (Atom)