Author Archives: ayn

Lightroom 3 rocks!

I tried every single version of Lightroom and Aperture, never liked the way they handled my photos. I always manually organize into a folder hierarchy loosely based on year and then month or event, looked at them in Bridge, and did most adjustments in ACR, and popped into PS for more adjustments or retouching.

A couple of my friends really like Lightroom so last weekend I decided to give Lightroom 3 a try, I had the beta a while ago and didn’t like it. After using it for a couple of days, all I can say is WOW, really awesome software for organizing and editing your photos. If you shoot a lot, and have photos stored across different hard drive volumes, you should use Lightroom 3. Here are a couple of things it really does right:

  • It imports photos into directory structure of your choosing, by default it creates top-level folders by year, and sub-folders of dates, which is pretty much how I did it manually before
  • It is fast! Lightroom 3 and CS5 are finally all Mac native and 64-bit, everything is very very fast
  • After importing about 35k photos across 3 different locations, I can easily find photos based on Metadata. For example, it’s easy to create a smart folder with all photos shot with a particular camera (by model or even serial number), I also have folders of shots with particular lens
  • There is an awesome plugin to run through a selection of photos (or all photos, if you want) and shows you a plot of the focal lengths you use. This is great when I was deciding between which lens to acquire. Here’s how my bar chart looks like, and this was run on all photos (well almost all, I deleted a lot of paid shoots coz I don’t need to keep them) shot with my copy of 5D Mark II:
  • Jeffrey's Focal-Length Plot
    Uploaded with plasq‘s Skitch!
  • For most things you can do in ACR, you can do in Lightroom, unless you’re retouching, there is almost no reason to open images in Photoshop. But when you do, Lightroom has tight integration with Photoshop and it works great
  • It supports dual-screen mode, this is really awesome if you have dual display! I can have a grid on 1 display with the Loupe view on another, this is much faster than zooming in and out in a single display
  • It publishes directly to Flickr, it syncs comments, views, title, caption, and tags. If you edit photos that have already been published to Flickr, you can simple re-publish and it will figure out how to replace the photos on Flickr instead of uploading new ones. When you use a publish service, it automatically converts to the desired format for upload and then delete them, which is really nice. Before I manually did that in Photoshop and was a pain to convert a lot of them (even converting in batches in Bridge was annoying)
  • It’s really easy and fast to sort through your entire photo library by metadata, ratings, tags, etc

Migrating a “degraded” Amazon EC2 Instance

I’ve been using AWS for a few years now, and it has been rock solid. Last Sunday one of my sites became unreachable, when I got home a couple of hours later, I was able to ssh into the instance and everything seemed to be working perfectly. I checked utmp logs and the instance was rebooted. A while later I got this email from Amazon:

From: Amazon EC2 Notification Subject: Notice: Degraded Amazon EC2 Instance Hello, We have noticed that one or more of your instances are running on a host degraded due to hardware failure. i-xxxxxx The host needs to undergo maintenance and will be taken down at 12:00 GMT on 2010-06-23. Your instances will be terminated at this point. The risk of your instances failing is increased at this point. We cannot determine the health of any applications running on the instances. We recommend that you launch replacement instances and start migrating to them. Feel free to terminate the instances with the ec2-terminate-instance API when you are done with them. Sincerely, The Amazon EC2 Team Sounded like they would terminate the instance because of hardware failure, and that would be very bad – this is a high volume eCommerce site. I looked around to see what was the best way to “clone” the instance and relaunch it, and it turned out to be really simple. When I setup EC2 stuff I always use an EBS volume for the important data like the /home, the MySQL storage, most of the configurations in /etc like Apache vhost configs. I also use an Elastic IP address so I can switch it to another instance easily, and it won’t require modifying DNS records at all. So all I had to do was:

  • get all your AWS access keys, certs, and user id, onto the instance
  • create a folder for the AMI bundling work
  • bundle the root volume on the dying instance
    $ sudo mkdir /mnt/ami && sudo ec2-bundle-vol -d /mnt/ami -k pk-CKXXXXXXXXXXXX.pem -u 12345678 -c cert-CKXXXXXXXXXXXXXXX.pem
  • upload the bundle to S3 and register the AMI
    $ ec2-upload-bundle -b somesite-post-degraded -m /mnt/ami/image.manifest.xml -a XXXXXXXXXX -s XXXXXXXXXXXXX/00XX
    $ ec2-register somesite-post-degraded/image.manifest.xml
  • launch a new instance with the AMI
  • unattach the EBS volume from the old instance
  • attach the EBS volume to new instance
  • re-assign elastic IP to new instance You can do a lot of these tasks from the

AWS Management Console. All of that took about 2 hours, most of the time was spent waiting for the AMI to bundle and upload as it was pretty large. Everything worked perfectly after the migration, when I set up the EC2 infrastructure I had planned for things like these and in theory migration should go without any glitch, but I never actually had a need to migrate an instance. It’s good to know that everything actually worked as designed.

Extra space/row in UIPopoverController content?

If you’re using UISplitViewController in your iPad/Universal apps, you probably implemented the UISplitViewControllerDelegate to add a UIBarButtonItem to the detail view controller’s toolbar to display a popover. The popover might have some extra space before the first row after you rotate the simulator or the iPad from landscape to portrait, took me a few hours to figure this out, it is because the navigation bar’s translucent property is set to YES, set it to NO before you add the button and you won’t see the extra space. Also, if you instantiate the nav bar in your NIB, leave its style asdefault in Interface Builder, otherwise the popover will be messed up on load when you launch the app in portrait orientation. Set them in code instead.

- (void)viewWillAppear:(BOOL)animated {
	[super viewWillAppear:animated];
	
	self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
	self.navigationController.navigationBar.translucent = YES;
}

#pragma mark -
#pragma mark UISplitViewControllerDelegate methods

- (void)splitViewController:(UISplitViewController *)svc willHideViewController:(UIViewController *)aViewController withBarButtonItem:(UIBarButtonItem *)barButtonItem forPopoverController:(UIPopoverController *)pc {
	self.navigationController.navigationBar.translucent = NO; //if I don't do this we get extra space in popover
	barButtonItem.title = @"Some Title";
	
	// Keep references to the popover controller and the popover button, and tell the detail view controller to show the button.
	self.popoverController = pc;
	self.rootPopoverButtonItem = barButtonItem;
	UIViewController <SubstitutableDetailViewController> *detailViewController = [splitViewController.viewControllers objectAtIndex:1];
	[detailViewController showRootPopoverButtonItem:rootPopoverButtonItem];
}

- (void)splitViewController:(UISplitViewController *)svc willShowViewController:(UIViewController *)aViewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem {
	//setting it back to black translucent
	self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
	self.navigationController.navigationBar.translucent = YES;
	
	// Nil out references to the popover controller and the popover button, and tell the detail view controller to hide the button.
	UIViewController <SubstitutableDetailViewController> *detailViewController = [splitViewController.viewControllers objectAtIndex:1];
	[detailViewController invalidateRootPopoverButtonItem:rootPopoverButtonItem];
	self.popoverController = nil;
	self.rootPopoverButtonItem = nil;
}

Speeding up Core Data-based UITableViewController

It is pretty common for an iPhone/iPad app to make an API call to a server, get the JSON response data back, parse that data, and display it in a table view. The usual way to do this looks like this:

- (void)apiCall {
	NSString *urlString = [[NSString alloc] initWithFormat:@"%@/some_models/some_action.json", apiEndpoint];
	NSURL *url = [[NSURL alloc] initWithString:urlString];
	NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
	[request setHTTPMethod:@"GET"];
	[NSURLConnection connectionWithRequest:request delegate:self];
	[url release];
	[urlString release];
}

This fires off the API call asynchronously, and then you implement some delegate methods like this:

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
	responseData = [[NSMutableData alloc] initWithCapacity:[response expectedContentLength]+100];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
	[responseData appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
	NSString *jsonString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
	[self dictionaryToCoreData:[jsonString JSONValue]]; //this parses the JSON data and persists into Core Data
	[jsonString release];
	[responseData release];
}

This approach works fine, but when you run it on the real devices, you might notice that the table view locks up when you go between the navigation flow. Basically the table view ignores user inputs until everything above is finished. This is because everything is performed on the main thread and it locks up the UI. The asynchronous NSURLConnection method used above doesn’t like to be used in a background thread, and there is really no reason to do things asynchronously if you’re working in the background, conveniently, there is a +sendSynchronousRequest method that waits until we get the response and data in NSURLConnection. To perform the above API call in a background thread, the code is actually much simpler:

- (void)apiCall {
	[self performSelectorInBackground:@selector(backgroundApiCall) withObject:nil];
}

- (void)backgorundApiCall {
	@synchronized(self) {
		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool

		NSString *urlString = [[NSString alloc] initWithFormat:@"%@/some_models/some_action.json", apiEndpoint];
		NSURL *url = [[NSURL alloc] initWithString:urlString];
		NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
		[request setHTTPMethod:@"GET"];
		
		NSURLResponse *resp;
		NSError *error;
		NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&resp error:&error];
		// you should probably do some error handling here
		NSString *jsonString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
		[self dictionaryToCoreData:[jsonString JSONValue]];
		[jsonString release];		
		[url release];
		[urlString release];
		[pool release];
	}
}

Now, when the table view is first loaded, it displays the stale data in Core Data, the API call is fired off in the background, and when we get data back from the call the table view is updated with the new data. Stale data is better than locked up UI.

Notice the very first time you run the app the table view will be empty until the background thread finishes, if this bothers you, you can preload the database with sample data. In our app we can’t really do this as the data is unique to the user, but it might make sense for you to ship your apps with pre-loaded data.

[Reachability reachabilityForLocalWiFi] crash with OS4 SDK

If you’re using Apple’s reachability framework and compiling with OS4 beta SDK, you might get a crash like this:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** +[Reachability<0xf37d0> init]: cannot init a class object.'

You can fix it by commenting out the [super init] line in + (Reachability*) reachabilityForLocalWiFi in Reachability.m:

+ (Reachability*) reachabilityForLocalWiFi;
{
	//[super init];
	struct sockaddr_in localWifiAddress;
	bzero(&localWifiAddress, sizeof(localWifiAddress));
	localWifiAddress.sin_len = sizeof(localWifiAddress);
	localWifiAddress.sin_family = AF_INET;
	// IN_LINKLOCALNETNUM is defined in <netinet/in.h> as 169.254.0.0
	localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM);
	Reachability* retVal = [self reachabilityWithAddress: &localWifiAddress];
	if(retVal!= NULL)
	{
		retVal->localWiFiRef = YES;
	}
	return retVal;
}