July 27, 2010

Android browser bug: pinch/zoom kills setTimeout()

I'm working on some cross-platform/mobile touch/mouse code for a fancy html/js UI, and everything's been working great, but when I pinch/zoom the web page in an Android browser, my setTimeout() calls stop running. To be safe, I recommend using setInterval() instead.
// before:
setTimeout( function() { runTimer(); } , 1000/30 );
function runTimer() {
    // update graphics here
    setTimeout( function() { runTimer(); } , 1000/30 );
}

// after:
setInterval( function(){ runTimer(); }, 1000/30 );
function runTimer() {
    // update graphics here
}
I initially thought that my touch events (touchstart, touchmove, touchend) were randomly failing after zooming, because my custom motion code would completely break after running at a solid 30+ fps. It appears that this is a known bug in pre-2.2 (Froyo) Android web browsers: http://code.google.com/p/android/issues/detail?id=8566

July 25, 2010

PHPFlicker: Get images by user's tag

I started using phpFlickr (http://phpflickr.com/), so I could use Flickr as a query-able backend for some of my images. I'm tagging images that I want to show up on certain pages of my site, but there wasn't an example for using tags on the phpFlickr examples page. So here's an example:
<?php
 $api_key = "YOUR_API_KEY";
 $user_id = "YOUR_USER_ID";
 $secret = "YOUR_SECRET";
 
 require_once("./php/flickr/phpFlickr.php");
 $f = new phpFlickr($api_key, $secret);

 $photos = $f->photos_search(array( "api_key"=>$api_key, "user_id"=>$user_id, "tags"=>"promo", "tag_mode"=>"any", "extras"=>"original_format,tags,url_o,description") );
 
 // Loop through the photos and output the html
 foreach( (array)$photos['photo'] as $photo ) 
 {
  // get original, or large if no original
  if( isset( $photo['url_o'] ) ) 
   echo '<a rel="lightbox[flickr]" title="'. $photo['title'].' - '. $photo['description'].'" href="'. $photo['url_o'] .'">';
  else
   echo '<a rel="lightbox[flickr]" title="'. $photo['title'].' - '. $photo['description'].'" href="'. $f->buildPhotoURL($photo, "large") .'">';

  echo '<img border="0" alt="'.$photo[title].' - '. $photo['description'].'" title="'.$photo[title].' - '. $photo['description'].'" src="' . $f->buildPhotoURL($photo, "square") . '" />';

  echo "</a>";
 }
?>

Just replace YOUR_API_KEY, YOUR_USER_ID (something like: 38845956@N05), and YOUR_SECRET (something like 7fc67607bd5abc59). This type of search is "secure", meaning that you have to acquire a secret key via the Flickr API.

This script will display square thumbnails that link out to the largest available image size. This can easily by styled, augmented with a lightbox javascript, or customized via the phpFlickr search options.

Enjoy.

July 5, 2010

Javascript: iPad orientation class with Prototype.js

I'm writing an iPad app in "HTML5", and I wanted to keep track of the device orientation. I wrote this little class to send notifications when it changes, and to always have simple access to the current state. This requires Prototype.js, but could easily be ported to another OOP style.
var AppState = Class.create({
 PORTRAIT: 0,
 LANDSCAPE: 1,
 orientation: -1,    
 initialize: function() {
  this.orientation = this.PORTRAIT; // default for desktop browser
  this.setUpOrientationListener();
 },
 setUpOrientationListener : function() {
  // add listener to window if it's orientation-capable
  if( window.orientation !== undefined )
  {
   var self = this; // handles scope
   window.onorientationchange = function (event)
   {
    if ( Math.abs( window.orientation ) % 180 == 90 )
    {
     self.orientation = self.LANDSCAPE;
    }
    else
    {
     self.orientation = self.PORTRAIT;
    }
    // send out custom event
    var containerNode = $$('body');
    containerNode[0].fire("app:orientationchange", { orientation: self.orientation });
   }
   // make sure local flag is set right away
   window.onorientationchange(null);
  }
 }
});


/*
// example code for listening to custom event that fires on orientation change
document.observe("app:orientationchange", function(event) {
 console.log( "Tag " + event.target.tagName + " with id of " + event.target.id + " says the orientation is now " + event.memo.orientation + ".");
});
*/

/*
// class initialization
var app_state = new AppState(),
*/

May 27, 2010

iPhone: UISegmentedControl with custom colors

[UPDATE]: We had an app approved with this code. Keep in mind that Apple has very inconsistent policies regarding app approval, but it's looking good for now.

Tasked with building a UISegmentedControl with different colors for selected/unselected buttons, I created a subclass that accomplishes this by digging through the subviews of the segmented control. I can't verify that this will get approved by the mysterious app store process, nor can I say it will function properly if you're using all of the built-in functions of UISegmentedControl (adding/removing segments dynamically will break this code). But for a simple case, it's working well for my purposes. Here we go:

CustomSegmentedControl.h :
#import 


@interface CustomSegmentedControl : UISegmentedControl {
 
 UIColor *offColor;
 UIColor *onColor;
 
 BOOL hasSetSelectedIndexOnce;
}

-(id)initWithItems:(NSArray *)items offColor:(UIColor*)offcolor onColor:(UIColor*)oncolor;
-(void)setInitialMode;
-(void)setToggleHiliteColors;

@end

CustomSegmentedControl.m :
#import "CustomSegmentedControl.h"


@implementation CustomSegmentedControl


-(id)initWithItems:(NSArray *)items offColor:(UIColor*)offcolor onColor:(UIColor*)oncolor {
 if (self = [super initWithItems:items]) {
        // Initialization code
  offColor = [offcolor retain];
  onColor = [oncolor retain];
  hasSetSelectedIndexOnce = NO;
  [self setInitialMode];
  [self setSelectedSegmentIndex:0];  // default to first button, or the coloring gets all whacked out :(
    }
    return self;
}

-(void)setInitialMode
{
 // set essential properties
 [self setBackgroundColor:[UIColor clearColor]];
 [self setSegmentedControlStyle:UISegmentedControlStyleBar];
 
 // loop through children and set initial tint
 for( int i = 0; i < [self.subviews count]; i++ )
 {
  [[self.subviews objectAtIndex:i] setTintColor:nil];
  [[self.subviews objectAtIndex:i] setTintColor:offColor];
 }
 
 // listen for updates
 [self addTarget:self action:@selector(setToggleHiliteColors) forControlEvents:UIControlEventValueChanged];
}

-(void)setToggleHiliteColors
{
 // get current toggle nav index
 int index = self.selectedSegmentIndex;
 int numSegments = [self.subviews count];
 
 for( int i = 0; i < numSegments; i++ )
 {
  // reset color
  [[self.subviews objectAtIndex:i] setTintColor:nil];
  [[self.subviews objectAtIndex:i] setTintColor:offColor];
 }
 
 if( hasSetSelectedIndexOnce )
 {
  // this is super weird - the subviews array is backwards... so deal with it like that
  [[self.subviews objectAtIndex: numSegments - 1 - index] setTintColor:onColor];
 }
 else
 {
  // ...but the very first time, they're the expected order :-/
  [[self.subviews objectAtIndex: index] setTintColor:onColor];
  hasSetSelectedIndexOnce = YES;
 }

}


@end 
And to initialize :
NSArray *toggleItems = [[NSArray alloc] initWithObjects:@"One",@"Two",@"Three",nil];
CustomSegmentedControl *toggleNav = [[CustomSegmentedControl alloc] initWithItems:toggleItems offColor:[UIColor blackColor] onColor:[UIColor redColor] ];
[toggleNav addTarget:self action:@selector(handleToggleNav:) forControlEvents:UIControlEventValueChanged];
[toggleNav setFrame:CGRectMake(52, 8, 211, 25)]; 
[self.view addSubview:toggleNav];
[toggleNav release];

Otherwise, follow the documentation for a UISegmentedControl, and enjoy.

iPhone: Bug in UITextField component - setTextColor not working

I'm working on a form for an iPhone app that does real-time form validation to let the user know if they're trying to create an account with a username that already exists. To let the user know that a username is already taken, I set the UITextField textColor property to red:
#define kColorRedError [UIColor colorWithRed:1 green:0 blue:0 alpha:1.0]
//... UITextField *username = [[UITextField alloc] initWithFrame:CGRectMake(12, 14, 296, 30)];
[username setTextColor:kColorRedError];
This wouldn't update until I typed another letter into the UITextField, so quite often it would display an error when it shouldn't, and vice versa. I tried using setNeedsDisplay and some other bits of code to try to force a display update after setting the text color. Nothing worked, until I tried this:
[password setTextColor:kColorRedError];
username.text = username.text;
...Quite absurd, but forces a redraw on the component. Gotta love those Apple components ;)