Feb 02

Apple’s rules and mechanisms for selling content in apps have been “evolving” since the App Store opened.

In app commerce variations

One of our very early projects for a client was a wallpaper app where you could download a number of wallpaper images paid for by the app purchase price. After that you could buy additional credits for more downloads. Initially Apple told us to use our own payment mechanism for this, as this was before In App Purchases were available. We implemented a solution using Bango, which Apple then promptly told us to remove or risk rejection. We now use IAP and all is well.

An ebook reader app that I worked on used the one app per book model, which does not scale very well. When IAP became available, this app was used as a showcase in the keynote at WWDC. The purchasing model is still ok, but it appears that the content may not be. More about that below.

For a magazine reader app, an existing ecommerce back-end was used for purchasing single issues as well as subscriptions. The best usability scenario would have been to seamlessly integrate the ecommerce website in a web view. But on the evening before the launch, Apple suddenly had a change of heart and requested (i.e. demanded) that we quit the app and launch Safari for all ecommerce processing.

Amazon does the same thing with their Kindle app, as does Barnes & Noble with their nook app. Interestingly Amazon’s Windowshop app does allow you to buy Kindle editions from within a web view, but not from the native UI. For mega retailers like Amazon, these restrictions and gray areas just become absurd.

What change?

Although not ideal, the launch Safari approach works, and is being used by many apps currently on the App Store for selling content. The fact that so many apps that use this mechanism have been approved, has led us to believe that this had Apple’s blessing. Until now.

The paragraph in question is 11.2 in the
App Store Review Guidelines. The wording here is very vague and depending on how you interpret that single sentence, you can argue that all of the above scenarios should be fine, or all should be rejected. If we accept the premise that Apple’s review and approval process is not random (and I know that to some, this may be a leap of faith), then the interpretation and enforcement of this paragraph must have changed over time.

SKU limit

If Apple required Amazon or B&N to use IAP for their ebooks, they would very quickly run into the max SKU limit of the IAP system. I have heard that the max number of In-App Purchase product items you can have is either 3,000 or 5,000. Although I suspect anyone trying to enter that many items using iTunes Connect would go mad long before approaching that number.

Several of our clients find that IAP is a non-starter because of this limit. Yes I know that you can use consumable IAP items to represent multiple real life SKUs, but that leads to other issues.

Subscriptions

Subscriptions have been part of IAP since the beginning, albeit the functionality was so crippled that I have not seen any project use it. In a nutshell each developer had to manage all aspects of subscriptions beyond processing the payment. And the big deal breaker was that subscriptions did not renew unless the customer opened the app regularly.

Today News Corp launched the much rumored The Daily news app. The news here for developers is that The Daily implements a new subscription system. Not much is known about the system, but the updated iTunes terms of service provides some clues:

In App Subscriptions Terms of Service
(Oddly the printable version of the terms of service does not include this new paragraph.)

It seems clear that these “Paid Subscriptions” do automatically renew until you explicitly cancel. And Apple will provide a mechanism for opting in to sharing your personal information with the publisher, which has been another point of contention between the newspaper/magazine industry and Apple.

Apple’s Eddy Cue says that the new subscription system is “Available on the Daily today, and there’ll be an announcement for other publishers soon.”

Books should be sold in the iBookstore

Having developed many book related apps I was surprised to hear about this rejection of an ebook app. Apparently if your ebook app does not offer any features beyond what an ePub file in iBooks can do, then it will be rejected and you will be asked to submit your book to the iBookstore instead.

I can understand Apple’s position on this from both a usability perspective (the book category is already the largest in the App Store) and from a profit perspective. But I sure don’t want to be on the app review team trying to decide where the line of “enough interactivity” goes.

What now?

My main gripe as a developer is of course the lack of proactive communication from Apple on policy changes like this. When they finally published the App Store Review Guidelines it was clearly stated that it was a “living document”. Well, it’s been pretty dead and void of changes since the initial publication. And I think that most developers would agree that there have been some significant changes since then.

If you currently have an app on the App Store that does not comply with all the latest interpretation and enforcement changes, then it’s unlikely that your app will be pulled from the store immediately. But if you ever want to make an update to that app, you will have to comply with the latest policy/rule/interpretation/enforcement. Even if this means that you have to make fundamental changes to your business.

written by Nick \\ tags:

Jan 07

Where is it?

At first I was a bit perplexed because I could not find the Mac App Store. I looked in iTunes, then I updated iTunes, but still no Mac App Store. A little bit later when the Software Update reminder told me there was an update available to OS X, then the lightbulb when on. Obviously a new OS update would be required to do the install and update magic that is part of the Mac App Store. (A least that makes sense to a developer.) It seems that I was not the only one who was confused. Now Apple has added a tutorial on how to get the Mac App Store to their website.

Pricing

I’m happy to see that the general price level in the Mac App Store is significantly higher than in the iOS App Store. Here’s a detailed analysis of the titles available on opening day.

This is mainly because of existing software titles from Apple and indy developers anchoring the price spectrum with unchanged prices from their other distribution channels. It is probably also reflection of the economics of selling to a smaller market.

Let’s hope that the Mac App Store does not fall into the price-race-to-the-bottom trap, so that it can become a viable channel for indy developers.

UI Design

Apple has been pretty strict in enforcing the HIG for iOS apps, so it’s a bit surprising that they have lowered the bar for the Mac App Store. Hopefully this is just a temporary lapse “justified” by the need to have a nice round number of 1,000 apps available on opening day.

This is especially sad since Mac developers have generally prided themselves in creating beautiful software with excellent interface design. One designer’s head exploded when he saw some of the apps in the Mac App Store, and he created this blog to showcase the worst.

DRM

It looks like some developers did not read the memo about protecting their apps. Check out Alex Curylo’s ValidateStoreReceipt code if you need it.

Updates

One of the benefits of the Mac App Store is that installed applications can be updated with one click, just like you update your installed iOS apps. One difference between iOS and Mac is that Mac applications can be installed from several sources. If you have already purchased an application directly from a developer and now it’s available in the Mac App Store, it’s likely that the Mac App Store will recognize that it has already been installed. Where the purchase button usually is you instead see “INSTALLED”. This is good because it will make it more difficult to purchase software that you already own. But the downside is that you are led to believe that you will receive updates to that title through the Mac App Store, which is not the case.

The best explanation that I’ve read on this says that the Mac App Store looks for a matching bundle identifier and version number. So it might be a good idea to change one or the other to avoid a lot of support headaches. Or you can just send your customers to this easy-to-remember URL: IfIBoughtYourAppAlreadyCanIUpdateItThroughTheMacAppStore.com

Another related question is how developers will deal with upgrade pricing. The lifeblood of many indy developers is the upgrade fees existing customers pay when a new major version is released. But in the iOS world customers have been trained to expect free updates for life for apps that they purchase. The reason this can work is the incredible volume of apps that you can sell to iOS customers, combined with the rapid growth of the platform. It will be interesting to see how this will play out on the Mac side.

Opportunity

If you are an iOS developer I think it can be a good business opportunity to port some of your apps to the Mac. Obviously this won’t work for all types of apps, for example if the primary use case depends on mobility or some device capabilities of the iPhone. Sadly I did not have any apps in the Mac App Store on opening day. But there is at least one title planned for the near future.

written by Nick

Jan 01

2010 was a fantastic year!

The Blog

This blog has grown to almost four thousand regular readers. For this I am humbled and very grateful. It gives me great joy to provide iOS programming advice, and I hope that you have learned something new in this past year.

I typically write about challenges, bugs, tips and tricks that I encounter myself as an iOS developer. It’s also a great repository of solutions for myself. I smile each time I search for a problem in Google, and on the first page of search results is a blog post that I wrote (hopefully) quite some time ago…

That said, I do welcome your input on topics, tutorials, difficulty level, mix between technical posts, business items and general ramblings.

One of my few New Years resolutions is to post more regularly.

Pervasent

In my day job as CTO of Pervasent, I’ve had the privilege to deliver many cool iPhone and iPad apps, work on worthwhile and engaging projects, and consult for enterprise class clients.

The limiting factor for our growth is to find more great iOS developers. (That’s why you’ve seen several open job posts on the blog.) Fortunately we’re now in the situation where top notch developers are contacting us, wanting to join our team. Working with great colleagues on a project is a lot more fun than working by yourself.

Here’s a funny anecdote from a recent job interview: One of the opening questions I like to ask is how long have you been programming in Objective-C. Most iPhone developers will answer somewhere in the range 1-3 years. Imagine my surprise when this individual answered 21 years! (He worked at both Next and Apple.) That rendered several of my followup questions moot…

What’s in store for 2011? More enterprise consulting work. But we’re also working on our own product in the document management space. More about that when we’re ready to release it.

Predictions

I’m not going to do any predictions for this year. When it comes to Apple’s mobile products their release schedule is now well established and we all know that there will be incremental updates to most products on this schedule. As for brand new categories of products, they’re just impossible to predict.

The general mobile device market is definitely exciting, but the macro trends are pretty obvious:

  • Apple will continue their curated, vertically integrated, high-margin business.
  • Android will become the device volume leader.
  • iOS will continue to command the most developer attention.
  • The App Store will remain the only financially viable marketplace for selling apps.
  • The other platforms (Windows Phone 7, RIM, Palm, Nokia) will continue to fall further behind.

I wish you a Happy and Prosperous 2011. Stay Hungry. Stay Foolish.

Update: Here are some predictions for 2011 that I found interesting, although I don’t necessarily agree with all of them.

written by Nick

Dec 30

I think we programmers always have projects that never seem to get finished. It could be an app that you’ve been working on for a long time, but it still needs some polish before you think it’s ready to ship. Not to mention all those great ideas that you have for new apps that you haven’t even stared working on.

Today Seth Godin has a great post about shipping something that scares you. He includes an intimidatingly long list of things he shipped in 2010. And then encourages readers to post their own lists.

So here’s my list of apps that I worked on and that shipped in 2010:

In addition there are a few projects in progress that are in review or are almost ready to submit. But that doesn’t count in this context.

The reason for publishing this list is not to brag (although I am quite proud of it), it’s to emphasize that the goal is to ship things. In our case that means getting the apps live on the App Store.

Need help getting things out the door? Checkout Seth Godin’s latest book Linchpin. And the accompanying ShipIt Workbook.

What have you shipped this year? Write a blog post, or submit a comment below. Don’t just post a link, write a story and tells us why shipping this product was scary.

written by Nick

Dec 24

Since April when I received my iPad, I have not purchased a single magazine or book in dead tree form. This was not a conscious decision or strategy, but merely a consequence of the convenience of iBooks and Zinio Magazine Reader.

Two eBooks that I recently read and enjoyed very much are Daemon and Freedom (TM) by Daniel Suarez. The story is about an MMOG that takes over the world. It’s extremely well written and the author is a programmer, so the technical details are believable.

Be sure to get both books. The first one — Daemon — ends without a satisfying conclusion, and Freedom will not make much sense without first reading the Daemon.

Since the iBookstore is not visible in iTunes and there’s no gifting option in iBooks, one way to give these books as a gift is to purchase a regular iTunes Gift Card or Gift Certificate. You may also find specific iBooks gift cards in some stores.

The iBookstore lives a somewhat schizophrenic life. You can’t search or browse books on the web or in iTunes, like you can with apps and music. But yet it is part of the iTunes payment infrastructure. The only way I’ve found to create links to specific books, like the ones above, is to use the Tell a Friend feature in iBooks.

written by Nick \\ tags: ,

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. :-D 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: , , , , , , ,