Friday, October 22, 2010

iPad External Screen Mirroring

At last week's Melbourne CocoaHeads meeting I did a presentation and demo of Locayta Search Mobile. What was cool about it (besides the content of course!) was that I did the whole presentation – including live demos – from the iPad, using the external VGA adapter. iPad's Keynote works well with the VGA adapter (besides a few niggles) as you would expect. However, there's no automatic mirroring of standard apps to the external display like there is on the Mac which makes live demoing a little more tricky.

So how did I perform live iPad app demos on the projector? Well, I didn't jailbreak to run background screen mirroring; and I didn't spend ages customising the demo apps to draw their UI on both the iPad and external displays (both of which are possible options).

I fortunately found a little hack on Google Code called iphoneos-screen-mirroring.  It simply provides a UIApplication category that gives you a single method to call to enable mirroring of the iPad display to the external display.  The minimum amount of code you'd need to add to your app delegate is:

#import "UIApplication+ScreenMirroring.h"

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    /* ... your app set up ... */
    // Start screen mirroring to Video Out (if connected)
    [[UIApplication sharedApplication]

The screen mirroring hack has no effect until an external adapter is plugged in to the iPad. When an external display is connected it kicks off a loop that continuously snapshots the main iPad display and copies the contents to the external display.  The mirroring frame rate can be customised when setting it up (like in the above example) and the reason to lower the frame rate is because the snapshotting kills the iPad performance quite a bit.  Not enough to prevent demoing of apps that don't refresh the display too much, but animations and scrolling suffer quite a bit.

For my case the hack worked well enough at 5 fps.  If you were trying to demo a 30 fps game I think you'd be a little disappointed.

Note that the mirroring hack uses UIGetScreenImage() to snapshot the main screen and so is not App Store "safe".

A similar project, robterrell's TVOutManager, claims to provide an App Store friendly technique (with certain caveats).  However, I found the performance to be slightly worse with that one and I wasn't planning on submitting the mirroring code to the App Store so I stuck with iphoneos-screen-mirroring.

Tuesday, October 12, 2010

Locayta Notes shows off iOS full text search

Locayta Notes
This week at Locayta we released an iPad/iPhone (universal) app to demonstrate the Locayta Search Mobile full text search library for iOS. Locayta Notes is a free iPad/iPhone note taking app which obviously offers full text search of the contents of all notes. Search is updated in real time as notes are being written or modified. It includes all the fancy search features such as word stemming, automatic spell correction, user defined synonyms and various sorting options.

But that's not all! Locayta has also released the full source code for Locayta Notes under an open source license. The source code should be a very useful resource for Locayta Search Mobile developers. All that is needed to build the app is the Locayta Search Mobile SDK, a free download from Locayta (currently in beta).

Finally, I will be doing a talk and demo of Locayta Search Mobile at this month's Melbourne CocoaHeads meeting on Thursday. Hope to see you there if you can make it.

Thursday, October 7, 2010

iOS 4 Background Task Completion for River Level

In the latest River Level update (v1.0.2) I added background task completion. River Level syncs waterway statistics from the server and even though the syncing is performed in a background thread while the app is running – so the user can still navigate around the app while data is being updated – if the user closes the app any active syncing task will be aborted.

iOS 4 allows tasks to complete in the background even after the user closes the app (e.g. hits the Home button or switches to another app). This is called background task completion and is part of the multitasking feature set. Adding background task completion was surprisingly simple. Now, River Level running on any device with iOS 4+ will complete any data syncing tasks in the background if the user closes the app while syncing is active, which is a better experience for the user.

The key to adding background task completion is to wrap any tasks in calls to the UIApplication methods: beginBackgroundTaskWithExpirationHandler: and endBackgroundTask:.

beginBackgroundTaskWithExpirationHandler: signals iOS that your app would like the task to complete even if the app is moved into the background state, before the app becomes suspended.  endBackgroundTask: signals iOS that your app's task has completed, allowing it to move your app to the suspended state if it needs to.  If the app remains in the foreground the whole time then these calls have no effect, so it doesn't hurt to use them.

Note that background tasks don't get free reign and don't get an indefinite period of time to complete. The "expiration handler" part of beginBackgroundTaskWithExpirationHandler: is a block specifying your cleanup code if the task out lives its lifetime.  Code executing as a background task must not make any UI updates or openGL calls (as the app is offscreen).

In River Level I use code similar to the example below.  If background tasks are supported on the host device (see Determining Whether Multitasking Support is Available) then I call beginBackgroundTaskWithExpirationHandler: to request background task completion before starting the syncing operation. When the syncing completes (and again, if background tasks are supported) and the task has not already been invalidated for some reason, I simply call endBackgroundTask: to tell iOS my task is done.  If the app was running in a background state at this time and no other background tasks were active, iOS would move the app to the suspended state.

if ([RiverLevelAppDelegate sharedAppDelegate].backgroundSupported) {
// Register to execute task in background if required
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;

// kick off the asynchronous syncing task...

// ... when the syncing task completes:

if ([RiverLevelAppDelegate sharedAppDelegate].backgroundSupported) {
// Flag completion of background task
if (bgTask != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplicationendBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;

Also note that you are not limited to only one background task, you can register as many as you need although they all must complete before the expiry time.  River Level registers a background task for each waterway when batch updates are requested.