Dec 06

Out of the blue we were contacted by Apple; they wanted to meet with us. It took us about two microseconds to say yes. 😀 So today we’re at the famous One Infinite Loop for a secret meeting. Regardless of the outcome of the meeting, we’re honored to have our work recognized and to have been invited.

As good fanboys we’ll have to visit the Apple Company Store on campus after the meeting to pick up “I visited the Mothership.” T-shirts and other swag that’s only sold there.

written by Nick

Nov 09

ShareKit has recently become one of my favorite iPhone open source projects. Just add a few lines of code to your app and you can instantly share text, URLs and other data via Email, Twitter, Facebook and a whole slew of other services.

I’ve found a couple of minor issues with the current release (version 0.2.1) of ShareKit, and I thought I’d share them to save you a couple of hours of debugging.

Error when building for device

This seems to be a known issue, that may depend on your Xcode install.

If you get 12 compiler errors when building for a device, starting with:

ShareKit/Core/SHK.m:35:28: error: objc/objc-class.h: No such file or directory

Then the solution is to change this import statement in SHK.m:

#import <objc/objc-class.h>

to:

#import </usr/include/objc/objc-class.h>

or:

#import <objc/runtime.h>

Hardcoding the path to something outside of Xcode or your project directories seems like bad idea. So I went with the second solution.

Conflicting Localizable.strings

The ShareKit project contains its own Localizable.strings files in the ShareKit / Localization directory. If your project is also localized there will be a conflict over which Localizable.strings file will be used at runtime.

There are a couple of ways to resolve this conflict:

  1. Remove the ShareKit files from your build target. This is the quickest solution, but you will lose the localizations provided in ShareKit (currently only German).
  2. Copy the contents of the Localizable.strings files from ShareKit to your Localizable.strings files. And then remove the ShareKit files from your build target. This works well when you have corresponding localizations.
  3. Rename the ShareKit files to ShareKitLocalizable.strings and then replace the NSLocalizedString macros in the ShareKit code with NSLocalizedStringFromTable where you can specify the ShareKitLocalizable.strings filename as the tableName.

written by Nick \\ tags: , , , , ,

Oct 21

On October 20th Apple announced several new products and technologies. Here are some thoughts and comments on how they might relate to us iOS developers.

Facetime

In the past 4 months Apple has sold 19 million Facetime capable devices (iPhone 4 and 4th generation iPod Touch). This is an astounding technology introduction rate. Does any other video conferencing technology come close to such a rapid expansion rate? Does any communication technology at all?

Apple is of course aware of Metcalfe’s law, so they need to spread Facetime to as many customers as possible to achieve the beneficial network effects. So it’s no surprise that they introduced Facetime for the Mac. Most people had probably assumed that Facetime would be a feature added to iChat, instead of a separate app. My guess it that they wanted to keep simplicity of Facetime: no setup, nothing to configure. Try to explain to your grandparents how to configure a Jabber account or an AIM account in iChat and then get the two of you connected via an audio chat.

A great way to further expand the network of Facetime nodes would be to provide an API to it. Let us initiate a Facetime call from within our own iOS apps, within an arbitrary view. Think of it as GameKit chat on steroids.

OS X Lion

The title of the event turned out to be a fun word play: they’re bringing some of the iOS features back to the Mac.

Some of the “features, like the iOS style app home screen, seem to be a bit gimmicky to bring to the Mac. But I will reserve judgement until I’ve tested it. At least it’s an interesting upgrade path for someone who starts with an iOS device to quickly learn to navigate OS X.

Auto save and auto resume are great features to have in an app. But just like in iOS, I’m assuming that the developer will have to do most of the heavy lifting. If Apple brings full auto resume to all of the iLife products, I’ll be impressed.

Mac App Store

The Mac App Store is intriguing. It takes all the benefits of distribution, payment processing and discovery from the iOS App Store and brings it to the Mac. It also comes with very similar App Store Review Guidelines. The best part is that it’s entirely optional. If you can live with the restrictions, then give it a try. There doesn’t seem to be any exclusivity clause in what I’ve seen.

One of the major drawbacks of the App Store is that developer’s don’t know who their customers are. For Mac indie developers, having a direct connection to their customers is often the lifeblood of their business. I wonder if Apple will allow a user registration screen in the apps?

The frictionless iOS App Store quickly led to rockbottom prices. Will the same scenario play out in the Mac App Store?

As primarily an iOS developer, I wonder if the same app review team resources will be used for both App Stores and will that lengthen the approval times for my iPhone and iPad apps?

MacBook Air

It’s interesting how Apple is out-engineering and out-muscling its competitors. Can any other company create a notebook with similar dimensions and match the build quality of the new MacBook Air? I’m probably well immersed in the reality distortion field, but to me the Air looks like a tough act to copy. And how are competitors going to follow suit with very large SSDs when Apple controls 40% of the flash memory market?

written by Nick

Oct 19

Yesterday I mentioned that wireless networks have high latency. I thought I’d expand on that and provide some statistics from a real world scenario.

High network latency means that it takes a long time for a packet to arrive at its destination. This is not a big deal when you’re downloading a large file or watching online video, because it just means that the start of the download is slightly delayed.

When communication goes back and forth and the other party has to wait for a packet to arrive before responding, that’s when high latency becomes a problem. This is particularly apparent when you open a new network connection to a server, because of the handshake packets between the client and the server.

Wireless networks are prefect examples of high latency networks. Therefore you need to consciously consider how to optimize data transfers in your iPhone/iPad apps. You should avoid multiple small server requests and instead try to do one large download that contains all the data in one request/response. Of course that may require changing the server API.

Here’s an example of why this may be worth your while:

A project I worked on recently needed to request URLs for image thumbnails for a large number of videos. The first – very naive – implementation looped through the video objects and requested each thumbnail URL. This resulted in 60 requests to the server, which took a total of 46 seconds over a wireless network. (Over a lower latency network the same number of requests took just 26 seconds. This illustrates the difference latency makes when you have many requests.) Of course waiting 46 seconds (or even 26 seconds) before the app is fully usable is not acceptable.

After a slight redesign, the app now creates an array of all the videos URIs and sends that to the server in one request. The response is an array of URLs. There are some other server requests that are necessary, so the total number of requests is now down to 8, a significant reduction from 60. And the total time to retrieve the same data is now reduced from 46 seconds to just 5.

It’s the same amount of payload data before and after the optimization. The performance difference is due to the overhead in creating network connections.

A performance improvement close to a factor of 10 is not bad for a few hours of coding.

written by Nick

Oct 18

You should always avoid downloading data over a wireless network whenever possible. If there is a file on a server that your app needs and it’s infrequently updated, then a good approach would be to cache that file in your app and only download it when an update is detected. (You could also package a version of the file inside the app bundle to provide a quick startup time the first time the app is launched.)

There are several ways to solve this problem. The code below does a HTTP HEAD request, checks the returned "Last-Modified" header, and compares it with updated timestamp of the local file. (A slightly more efficient approach would be to send a "If-Modified-Since" header with your request. But there seems to be some issues with that, so proceed carefully with that approach.)

High-level steps:

  1. Create a HTTP HEAD request.
  2. Read the "Last-Modified" header and convert the string to a NSDate.
  3. Read the last modification timestamp of the local file.
  4. Compare the two timstamps.
  5. Download the file from the server if it has been updated.
  6. Save the downloaded file.
  7. Set the last modification timestamp of the file to match the "Last-Modified" header on the server.

Please note that the code uses synchronous requests to make the code shorter and better illustrate the point. Obviously you should not call this method from the main thread. Depending on your requirements, you could rewrite the code to use asynchronous requests instead.

- (void)downloadFileIfUpdated {
	NSString *urlString = ... your URL ...
	DLog(@"Downloading HTTP header from: %@", urlString);
	NSURL *url = [NSURL URLWithString:urlString];
	
	NSString *cachedPath = ... path to your cached file ...
	NSFileManager *fileManager = [NSFileManager defaultManager];

	BOOL downloadFromServer = NO;
	NSString *lastModifiedString = nil;
	NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
	[request setHTTPMethod:@"HEAD"];
	NSHTTPURLResponse *response;
	[NSURLConnection sendSynchronousRequest:request returningResponse:&response error: NULL];
	if ([response respondsToSelector:@selector(allHeaderFields)]) {
		lastModifiedString = [[response allHeaderFields] objectForKey:@"Last-Modified"];
	}
	
	NSDate *lastModifiedServer = nil;
	@try {
		NSDateFormatter *df = [[NSDateFormatter alloc] init];
		df.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
		df.locale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease];
		df.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
		lastModifiedServer = [df dateFromString:lastModifiedString];
		[df release];
	}
	@catch (NSException * e) {
		NSLog(@"Error parsing last modified date: %@ - %@", lastModifiedString, [e description]);
	}
	DLog(@"lastModifiedServer: %@", lastModifiedServer);

	NSDate *lastModifiedLocal = nil;
	if ([fileManager fileExistsAtPath:cachedPath]) {
		NSError *error = nil;
		NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:cachedPath error:&error];
		if (error) {
			NSLog(@"Error reading file attributes for: %@ - %@", cachedPath, [error localizedDescription]);
		}
		lastModifiedLocal = [fileAttributes fileModificationDate];
		DLog(@"lastModifiedLocal : %@", lastModifiedLocal);
	}
	
	// Download file from server if we don't have a local file
	if (!lastModifiedLocal) {
		downloadFromServer = YES;
	}
	// Download file from server if the server modified timestamp is later than the local modified timestamp
	if ([lastModifiedLocal laterDate:lastModifiedServer] == lastModifiedServer) {
		downloadFromServer = YES;
	}
	
	if (downloadFromServer) {
		DLog(@"Downloading new file from server");
		NSData *data = [NSData dataWithContentsOfURL:url];
		if (data) {
			// Save the data
			if ([data writeToFile:cachedPath atomically:YES]) {
				DLog(@"Downloaded file saved to: %@", cachedPath);
			}
			
			// Set the file modification date to the timestamp from the server
			if (lastModifiedServer) {
				NSDictionary *fileAttributes = [NSDictionary dictionaryWithObject:lastModifiedServer forKey:NSFileModificationDate];
				NSError *error = nil;
				if ([fileManager setAttributes:fileAttributes ofItemAtPath:cachedPath error:&error]) {
					DLog(@"File modification date updated");
				}
				if (error) {
					NSLog(@"Error setting file attributes for: %@ - %@", cachedPath, [error localizedDescription]);
				}
			}
		}
	}
}

Line item notes:

03. See this post for the source to the DLog macro.

14. It’s always good practice to check if the returned object responds to a selector before you send a message to it. See this post for more details.

22. The "Last-Modified" timestamp on the server includes the name of the month as text. Set the locale of the date formatter to English (assuming that your server responds in English) otherwise the code will fail when run on devices in non-English countries. See this post for more details.

Keep in mind that wireless networks have very high latency. That means it’s very expensive to create a network connection to a server. If the size of the file you want to download is very small, then you will not gain much with the approach above since it will take almost as long time to get the header information as downloading the entire file.

written by Nick \\ tags: , , , , , , ,

Oct 15

How often have you been approached by somebody who has a great idea for an iPhone app, and they will tell you the idea if you just do the programming, and then you will both share in the profits?

As an aside, I wonder if this happens to book authors as well. “I have this great idea for the next blockbuster novel, if you just write it, we can both share in the profits from the book sales and the sure-to-come movie rights.” Why is programming generally considered to be a simple task once you have hatched the idea? It can’t be because of all the shining examples of software projects that go over budget and never deliver anything useful to its users. End of soapbox.

I was reminded of the old adage about ideas vs. execution when I watched the episode about Mark Zuckerberg in the Bloomberg Game Changers series. In the program the Winklevoss twins and Divya Narendra alleged that they gave Mark Zuckerberg the idea for Facebook. And therefore they demanded to be compensated for their contribution to the success of Facebook.

Of course they didn’t write a single line of code, they didn’t contribute a single hour of work to Facebook, but they had The Idea. In my book, that contribution is infinitesimally close to zero. To make the lawsuit go away, Facebook/Zuckerberg settled for $65 million. My advice would be to take that money and run. And maybe use it to pay someone very well to execute on your next idea if you’re unable to do it yourself. Instead they are now rejecting the settlement and are looking for a larger handout. Lame.

Another episode in the Bloomberg series is about Steve Jobs. Jobs and Apple represent the polar opposite: they are masters of execution. Many of the most successful Apple products were not original ideas hatched at Apple: GUI, MP3 player, laptop computer, smartphone, app store. But the key differentiator is that Apple executed on these ideas better than anyone else.

written by Nick

Sep 27

I am fortunate to be in a position to be selective about the clients I work with and the projects I work on. The project has to have some difficult technical challenge to satisfy the inner geek. But equally important is that the resulting app should do something good, something that helps people, and maybe make the world a little bit better.

An app that I recently completed fits these criteria perfectly. The app is called NutriSleuth and in a nutshell it allows people with allergies or other medical conditions to check out food items in the grocery store to see if they are going to make them, or anyone who they shop for, sick.

The app scans the bar code of a food item and uses the UPC to lookup the product in a database stored on the device. The database contains over 200,000 food items and over 2.5 million nutrition records. The database uses Core Data and its total size is over 500 MB on the device. Herein lies several technical challenges, and I’ll be writing a few blog posts about this.

Once the nutritional records for the product have been retrieved, a rules engine goes through the data to determine if the fits or clashes with the nutritional profiles the user has configured in the app. (This rules engine is part of the proprietary, secret sauce of this app, so unfortunately I cannot write about this in more detail.)

The result of the analysis is a simple green, yellow, red light that indicates if you can safely eat this food item, if you should exercise caution or put it back on the shelf immediately. Some people go into anaphylactic shock when they are exposed to certain food ingredients like peanuts, tree nuts, eggs, etc. This severity level can also be configured in the app and then the nutrition and ingredient analysis goes into hyper-vigilant mode.

The app does not just handle food allergies. Say that you have a friend who’s coming over for dinner and she has Crohn’s disease or a high cholesterol condition, or only eats vegetarian or only Kosher food. What can you safely serve for dinner? Setup a separate profile for you friend and scan away.

The app received a lot of press even before it was launched. I think the reason is that you instantly recognize that this app would be very helpful (potentially a lifesaver, literally) for so many people that have food allergies or other common medical conditions.

I’m proud to have been part of this project. And being able to do something really good like this, is one reason why I love being an iPhone developer.

You can check out NutriSleuth in the App Store now.

written by Nick \\ tags:

Sep 22

When you work with a static library (e.g. libCoolCode.a) for an iPhone project in Xcode, you can either have a fat binary that contains code for both the iPhone simulator and the iPhone device in the same file, or you may have two separate files that you need to add to Xcode.

Adding a single static library to Xcode is trivial. Here are the step by step instructions for the latter case.

1. Add the files to Xcode

Use the normal Add > Existing Files… to add both the header files and the library files to your project. The result should look something like this:

LibraryAndHeaderFilesAddedToXcode.png

2. Add the header files to the search path

A static library comes with one or more header files (.h) that you need to add to your project build settings. (This step is the same for a single library file as for multiple files.)

Open the build settings tab and find the “Header Search Paths” entry, and add the path to the directory with the header files.

AddHeaderSearchPathToXcodeBuildSettings.png

For this particular library there’s a model directory under the headers directory which also contains headers files. That’s why the Recursive checkbox is checked. The double quotes around the path are needed to handle paths with spaces.

If you try to build the project for the simulator at this point you’ll probably get a compiler warning like this:

file was built for unsupported file format which is not the architecture being linked (i386)

3. Remove the library files from the build target

Open the Targets tab and the target of your project and you’ll see that the library files you just added were also added to the list of libraries to link with.

LibraryFilesInTargetLinkFolder.png

Delete these two entries.

You may also have to delete these entries from the build settings. In the build settings tab find the Library Search Paths entry and delete the two entries.

DeleteLibrarySearchPathInXcode.png

That will get rid of the compiler warning above, but the linker will complain since the required libraries are no longer linked.

4. Add the libraries as other linker flags

Again go to the build settings tab and now find the Other Linker Flags (OTHER_LDFLAGS) entry.

Highlight the Other Linker Flags entry and click on the tool drop down button at the bottom of the window.

XcodeBuildSettingsToolsDropDownMenu.png

Select Add Build Setting Condition from the drop down menu. This will add a new line under the Other Linker Flags entry. Change the first drop down from Any SDK to Any iOS Simulator. Leave the second drop down as Any Architecture. In the value field enter the path and name of the library file built for the simulator. Repeat this step for the device build, selecting Any iOS Device. The result should look similar to this (your paths will of course be different):

OtherLinkerFlagsConditionalBuildSettings.png

Your project should now compile and run for both the simulator and the device.

written by Nick \\ tags:

Sep 20

Conference season is upon us again.

APPNATION

This new conference took place in San Francisco last week. It was more business oriented and not a developers conference. A couple of our clients attended and said it provided great networking opportunities and they even managed to get the press to write about their app.

JV Alert Live – Denver – September 24-26

This is not an iPhone developers conference either, but it’s a chance to see me on stage. I will be participating in panel discussions, providing a perspective on mobile business opportunities. This is a really good conference for networking.

iPhone/iPad DevCon 2010 – San Diego – September 27-29

Bills themselves as “the first major developer conference after the release of Apple’s iPhone 4 hardware and iOS 4”. A lot of good workshops and technical classes on the schedule.

Voices That Matter: iPhone Developers Conference – Philadelphia – October 16-17

VTM is back again with a great lineup of speakers. This time it’s on the east coast.

360|iDev – Austin, TX – November 7-10

One of my favorite conferences is 360|iDev. I will not be speaking this time, but at least one of my colleagues will be attending.

360|MacDev – Denver – December 10-11

The good folks at 360 Conferences are launching a new conference geared towards Mac Desktop developers. Picking up the torch after C4 ended.

written by Nick \\ tags:

Sep 17

So, I was exiting the Castro Street MUNI station, taking the escalator up to street level. I had my book bag over my shoulder behind my back and my iPad in my hand. I decided to hop up the last few steps and tripped hard on the last step. As I careened towards the ground (I knew I was going down) I put my left hand out and realized it contained my iPad. Rather than let if fly or shatter it as I put my hand down, I launched forward and planted an exaggerated right shoulder roll, cradling my iPad like a football. It never touched the ground. I stood up on my knees, embarrassed, laughing and letting others around me know I was okay …. but it was really about my iPad.

You don’t have to be this passionate about your iDevices to work here at Pervasent. But it helps. 🙂

written by Jess \\ tags: