Thursday, February 24, 2011

Locayta Search Mobile for iOS beta5

At Locayta we recently released beta5 of the Locayta Search Mobile full text search library for iOS.  This may be the last beta as we prepare for the 1.0 release soon.


Beta5 included the usual bugfixes and improvements to memory usage.  It also included a new feature that assists with search results display. The feature allows you to fetch the character ranges of all terms in a text field that were matched by a search result.


This feature isn't as simple as it sounds, due to the search engine's ability to infer relevant document terms by spell correcting, stemming or synonym expansion of query terms.  This means that terms matched in a document aren't necessarily the same as those in a query. The new character range fetching exposes this information so you can clearly highlight the term "test" in a document that matched the search term "testing", for example.


Here are some more examples.  Assume a note contains the line of text:

These notes are just for testing.

Each of these search queries would match this note by the highlighted terms:

  • "test note" : These notes are just for testing.
  • "test notr" : These notes are just for testing.
  • "I am not testing" : These notes are just for testing.
i.e. the same document terms were matched by each query, however in some cases stemming or spelling correction was required to get the match.


Another example: consider a note containing this line of text:

My Christmas list

With an explicit synonym defined to expand "xmas" as "christmas", a search for "xmas" would match the document on the term "christmas":
  • "xmas" : My Christmas list



Using the character range fetching feature for matching search terms gives you the information needed to highlight or find search terms in your documents.  This is demonstrated in Locayta Notes 2.0 (free in the App Store).  The screenshots above are from Locayta Notes, demonstrating the examples.





Thursday, February 10, 2011

Locayta Notes 2.0

Locayta Notes on iPad
One of my primary projects for Locayta has been the development of the Locayta Search Mobile full text search library for iOS.  As part of that work I built a note taking / text editing app for iPad/iPhone that helped us show off embedded full text search on mobile devices. Recently I got the chance to add some significant functional enhancements to this app, which has now been released: Locayta Notes 2.0.


The biggest feature in Locayta Notes 2.0 is integration with Dropbox which will automatically sync all your notes across all your iOS devices.  Notes will also be accessible as plain text files in Dropbox on your linked computers. This feature has been extremely handy for me personally, which made Locayta Notes become my primary note taking app on my iPad & iPhone.


But that's not all, here's the full list of features in Locayta Notes 2.0:


 • Instantly search the contents of all notes using Locayta's embedded full text search engine, Locayta Search Mobile. Includes smart search features such as automatic spell correction, synonyms, stemming and context snippets.


 • Dropbox syncing to automatically sync notes between all your iPads, iPhones and computers.


Locayta Notes on iPhone
 • Customise the text colour, size & font per note. Change the default text style.


 • Full support for printing notes with AirPrint (iOS 4.2+).


 • Full iOS 4.0 multitasking. Search index updates and Dropbox syncing tasks will complete in the background if the app is closed.


 • Share notes via email without leaving the app.


 • Universal app for both iPad and iPhone.


 • Free and ad-free!



The last "feature" is important. Locayta Notes is and will remain free, as it helps Locayta promote the Locayta Search Mobile full text search library on iOS devices.


Get Locayta Search Mobile here. It is free to download and develop with.


btw, Locayta Notes 1.0 was open sourced on github, as a resource for Locayta Search Mobile developers.



Thursday, February 3, 2011

Janken Battle

Janken Battle: Paper beats Rock!
I recently released a novelty iPhone game to the App Store: Janken Battle.  It is a networked Rock Paper Scissors simulator. You connect to an opponent, either nearby with Bluetooth, or anywhere in the world by Game Center matchup. To play, simply shake the device 3 times, like the kids do in real life. The game handles the rest!  On the 3rd shake, a result is chosen for you and matched against your opponent's result to determine the winner.  The game keeps track of scoring until the end of 3 to 9 rounds.


The result (rock, paper or scissors) is chosen randomly, with equal waiting for the 3 choices by default as you would expect.  However, an option for "advanced" users (if you could call them that) allows you to change the percentages, skewing the result towards your preferred choice.  Or, select a result outright by swinging the percentage to 100% for that choice, leaving the others at 0%.


Janken Battle: Strategy control
This release of the game is really a prototype of an idea I had and a good excuse to play around with and learn the iOS Game Center & GameKit APIs. One of my goals is to see how many modern game features and technologies I can fit into a Rock Paper Scissors game!  It already has:



• Bluetooth wireless play
• Remote Internet play
• Live in-game voice chat
• Online Leaderboards
• Achievements


The graphics & sound (and let's face it, gameplay) are still fairly basic, just what I could put together to get a release out the door. I have a bunch of interesting ideas in mind which should really enhance the game and novelty factor, assuming I get a chance to implement them.  I will probably push them out incrementally as time permits. 


For now, Janken Battle is free, so no excuses not to grab it for a quick play. I have embedded iAds while I consider options for how best to monetize enhanced versions down the line.

Wednesday, January 19, 2011

git push dropbox


Git is great and Dropbox is awesome and recently I found a way to combine the two.


I host my open source projects on github (as well as bitbucket – I'm a happy Mercurial user as well) but there are some private and/or client projects I don't want to store on those sites.


Git, of course, doesn't require a server repository.  So you can work locally while still having full version control.  However, I like the comfort of syncing to an offsite repository so I tend to push my changes to the remote server multiple times per day (also, old svn habits die hard).


For git repositories that I don't want hosted on github, I push to a git repository sitting in Dropbox instead. The push is handled locally and Dropbox performs all the magic of syncing to the cloud (my offsite backup) and to my other computers/devices (switching dev computers isn't a problem). Simple, fast, nice.


Here's how I set up a bare git repository in Dropbox for a project:


$ mkdir ~/Dropbox/src/git-server
$ git init ~/Dropbox/src/git-server/MyProject
$ cd ~/Dropbox/src/git-server/MyProject
$ git config --bool core.bare true

$ cd ~/src/project-git
$ git status
# On branch master
nothing to commit (working directory clean)
$ git push --mirror ~/Dropbox/src/git-server/MyProject

$ git remote add dropbox ~/Dropbox/src/git-server/MyProject
$ git push --mirror dropbox
Everything up-to-date



The first batch of commands initialises a new git repository.  The repository is flagged as bare so that we can push all branches (including the working branch).

The second batch simply pushes a development repository to the Dropbox one.  I use --mirror to push everything over.

The third batch of commands set up a shortcut for the Dropbox repository.  I name it "dropbox" so from then on I only have to do "git push dropbox" to push to it (with or without --mirror as required, although I usually use it).

Update: I have since refined my git/dropbox workflow: git sharing with dropbox.

Wednesday, December 22, 2010

CMPopTipView - a custom popup view for iOS


CMPopTipView
is a custom iOS UIView that I have released on github.  It is relatively simple, but solved a need I had in an app where I wanted a kind of speech bubble shaped popup to give the user a hint about where to tap next. (You might argue that good UI shouldn't need an extra hint for the user but then I would punch you in the arm and go about my day.)




CMPopTipView is simple to use.  Just initialise it with a target view, a container view and a message to display and it handles positioning itself with the container view to point at the target view, sizing itself to fit the text.  It is also smart about UIBarButtonItems (in either navigation or tool bars) and can position itself properly to point at them. Some example code is shown below.


Some features of CMPopTipView:

  • Automatically positioned in container view;
  • Positions pointer to point at target view (either above or below);
  • Automatically sizes itself to fit the text message;
  • Background and text colours can be customised;
  • User can tap CMPopTipView to dismiss it (with optional delegate callback);
  • Can be dismissed programmatically;
  • Not a modal view.

CMPopTipView draws itself using Core Graphics and so scales up nicely to the high resolution Retina display with no extra work.  It might be a useful reference for iOS developers looking for Core Graphics drawing examples.  The code demonstrates how to draw the path of a custom shape, how to draw a gradient fill within a clipped path, how to apply a shadow to the shape and how to draw text within the shape.


A universal (iPhone/iPad) demo app is included with the source code. It is a button-fest of popup tips, as shown by the screenshots.

Example 1 - point at a UIBarButtonItem in a nav bar:

// Present a CMPopTipView pointing at a UIBarButtonItem in the nav bar
CMPopTipView *navBarLeftButtonPopTipView = [[[CMPopTipView alloc] initWithMessage:@"A Message"] autorelease];
navBarLeftButtonPopTipView.delegate = self;
[navBarLeftButtonPopTipView presentPointingAtBarButtonItem:self.navigationItem.leftBarButtonItem animated:YES];

// Dismiss a CMPopTipView
[navBarLeftButtonPopTipView dismissAnimated:YES];

// CMPopTipViewDelegate method
- (void)popTipViewWasDismissedByUser:(CMPopTipView *)popTipView {
  // Any cleanup code, such as releasing a CMPopTipView instance variable, if necessary
} 




Example 2 - pointing at a UIButton, with custom color scheme:

- (IBAction)buttonAction:(id)sender {
  // Toggle popTipView when a standard UIButton is pressed
  if (nil == self.roundRectButtonPopTipView) {
    self.roundRectButtonPopTipView = [[[CMPopTipView alloc] initWithMessage:@"My message"] autorelease];
    self.roundRectButtonPopTipView.delegate = self;
    self.roundRectButtonPopTipView.backgroundColor = [UIColor lightGrayColor];
    self.roundRectButtonPopTipView.textColor = [UIColor darkTextColor];

    UIButton *button = (UIButton *)sender;
    [self.roundRectButtonPopTipView presentPointingAtView:button inView:self.view animated:YES];
  }
  else {
    // Dismiss
    [self.roundRectButtonPopTipView dismissAnimated:YES];
    self.roundRectButtonPopTipView = nil;
  }
}

#pragma mark - CMPopTipViewDelegate methods
- (void)popTipViewWasDismissedByUser:(CMPopTipView *)popTipView {
  // User can tap CMPopTipView to dismiss it
  self.roundRectButtonPopTipView = nil;
}
Get CMPopTipView from github.

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]
      setupScreenMirroringWithFramesPerSecond:5];
}

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.

Thursday, August 19, 2010

Looking for Beta testers for new iOS full text search engine library


At Locayta we have released a beta of our new full text search engine library for iPhone/iPad developers. I have been leading this project from the software engineering side and am looking for developers interested in beta testing this framework and offering some feedback.

To download the beta version, go to http://www.locayta.com/pages/new/ios-search-engine

Here is a quick blurb about the search engine library:

Locayta Seach for iOS is a port of Locayta's full text search engine library for the iOS platform.  The core library is pure C (with a bit of C++) and we have wrapped a higher level Objective-C API around it and produced a static library in a Framework bundle so that iPhone & iPad apps can provide fast local full text search.  The search engine provides "enterprise level" full text search using a probabilistic model of document terms, along with clever features to improve search success such as automatic spell correction (based on trigram analysis of terms) and word stemming.