Monday, May 23, 2011

CMPopTipView - new animation option

In December I introduced a custom iOS UIView: CMPopTipView, a view for presenting targeted tips or callout views.


CMPopTipView got a small update recently, a new presentation animation style submitted by myell0w.


The default animation style I originally implemented was a simple slide into place while fading in effect. Subtle, nice. This is still the default.


The new animation style option is a pop out style animation, like iOS UIAlertViews. This style could be useful when you need to catch the user's attention.


To enable the pop out style animation set the animation property to CMPopTipAnimationPop. For example:

CMPopTipView *popTipView = [[[CMPopTipView alloc] initWithMessage:@"My message"] autorelease];
popTipView.animation = CMPopTipAnimationPop;
popTipView.delegate = self;
[popTipView presentPointingAtView:button inView:self.view animated:YES];

Get CMPopTipView from github or read more details about this update from the pull request.

Monday, May 16, 2011

River Level 1.1



I recently updated River Level for iPhone to version 1.1. The main feature added to 1.1 is the ability to customise the units of measurement. Specifically:
  • River depth: meters or feet;
  • Water temperature: celsius or fahrenheit
River Level was created in Australia, initially for NSW waterways, so had only shown measurements in meters & celsius. After the addition of USA rivers boosted the app's popularity in America the option for feet/fahrenheit was requested by a lot of American users (understandably) so I was happy to find some time to add the option.


Units of measurement can be customised by going to system Settings / River Level.


To make the user experience even smoother, the first river you add determines the default units of measurement. So if the first river you add is an American river, the units automatically default to feet/fahrenheit. This should remove the need for most users to access Settings.



River Level is free in the App Store.

Thursday, April 7, 2011

Swap It Don't Stop It iPhone app

My recent handy work is out in the wild in the form of the Swap It Don't Stop It iPhone app for a new Australian Government health campaign.


The Swap It Don't Stop It campaign is all about educating and encouraging people to swap unhealthy food and unproductive activities for those that can improve your overall health & fitness.


The iPhone app provides a tool for tracking your "swaps", gaining achievements, receiving alerts to be reminded when to swap, tracking food swaps (with an integrated shopping list) and finding healthy activities near you.


Technically, the app required:

  • Custom views to match the attractive design;
  • Core animation for some custom transitions and UI elements;
  • Server side REST API integration;
  • MapKit and Location Services along with SOAP API integration for finding activities by location;
  • Core Data for local data management;
  • Push notifications for alerts;
  • Retina display optimisation.

The app looks really great and is free on the Australian App Store. I was very happy with how it turned out. I developed the app on behalf of the great guys at Millipede Creative Development and JWT Melbourne.


Monday, April 4, 2011

iOS Dev, Beta & Production builds installed side-by-side

A development and testing pattern I adopted early on for my iOS projects was to configure projects such that development, ad hoc test and release builds could all be installed on my device at the same time. This allows for side-by-side testing and easy comparison of the builds, each being at different states of completion at any point in time.


For example, this iPhone screen snapshot demonstrates dev, beta & release builds of my hobby project River Level installed side-by-side.


Dev, beta & release builds of River Level installed side-by-side
iOS differentiates apps by their Bundle Identifiers (CFBundleIdentifier) which are defined in the Project-Info.plist.  A Bundle Identifier looks like a reverse DNS address, e.g. "com.apple.safari". What I like to do is to set the Bundle Identifier to different values for each build configuration, thus allowing each to be installed separately. I don't do this manually, though, as I'll explain below.


Note that some iOS features – like Push Notifications, Game Center and In-App Purchase – require the Bundle Identifier to match the value configured in iTunes Connect so you can test these features. In those cases this technique won't be effective, unless you set up multiple app instances in iTunes Connect for each build configuration.


The other parameter I customise per build configuration is the Bundle Display Name (CFBundleDisplayName). This is the app name as displayed on the home screen. iOS doesn't require these names to be unique, so if you changed the Bundle Identifiers without changing the Display Names you would end up with multiple instances of your app all with the same name and icon, making it difficult to work out which build is which.


My convention is to append "∆" to dev builds and "ß" to ad hoc builds.


To customise the Bundle Identifier and Bundle Display Name per build the process is:

  • Append ${BUNDLE_ID_SUFFIX} to the "Bundle identifier" in Project-Info.plist.
  • Append ${BUNDLE_DISPLAY_NAME_SUFFIX} to the "Bundle display name" in Project-Info.plist.
Custom variables appended to Bundle Display Name and Bundle Identifier values



Note that with no other changes these variables will be expanded to empty strings and so will have no effect. This is how I handle Release builds, so they are submitted to the App Store with standard values.


The next step is to define the custom values for each build configuration.  My convention is:


Development (Debug) builds:
  BUNDLE_DISPLAY_NAME_SUFFIX=∆
  BUNDLE_ID_SUFFIX=.dev


Ad Hoc builds:
  BUNDLE_DISPLAY_NAME_SUFFIX=ß
  BUNDLE_ID_SUFFIX=.adhoc

Release builds:
  Neither of these settings is defined.


The place to put these settings is in each relevant Build Configuration of the Project. Note: some people edit the build configuration for their Target, but I recommend only doing this if you need a setting specifically for a build target.  If you only have one target, edit the build settings for the whole Project.  That way, if you add Targets later they will inherit the Project build settings.


To add custom settings to build configurations, in Xcode 3:

  • Select menu "Project" / "Edit Project Settings"
  • Select "Build" tab
  • Select a build configuration in the Configuration list (e.g. "Debug")
  • Select "Add User-Defined Setting" from the command list (bottom left corner pulldown)
  • Add a user-defined setting for each of "BUNDLE_DISPLAY_NAME_SUFFIX" and "BUNDLE_ID_SUFFIX" with the values you choose.
  • Repeat this for each build configuration that you want to install separately to the others.

User-defined settings for a Debug build configuration
In Xcode 4, the process is similar, although the UI has changed.  Select your project; select the "Build Settings" tab, then select "Add Build Setting"/"Add User-Defined Setting". You can set the values for Debug and Ad Hoc at the same time with the new UI.


To see any user-defined settings, scroll the build settings table to the bottom; or use the "Show" list to filter only "User-Defined Settings" (Xcode 3).


To delete a user-defined setting, highlight the setting and hit the delete key.


That's it. Now re-build and install and the app should appear separately to the other builds. Feel free to install as many different builds as you feel like.

Tuesday, March 8, 2011

iRaspberry 1.6

Recently I had the pleasure of working with the Lycette Bros, hired to update their cool iRaspberry iPhone apps (App Store: iRaspberry Lite [free], iRaspberry Pro [AU$1.19]).


iRaspberry is a simple but fun concept that has your iPhone stand-in for your mouth expressions! It comes with a load of video clips of various mouths performing raspberries, tongue pokes, kisses and other actions and you simply choose an action and hold it in front of your mouth to really show people how you feel about them.




I helped the Lycette Bros produce the 1.6 release, which added a bunch of new video clips, updated the video handling & in-app purchase code for iOS SDK 4.x and added iAds to the free lite version.


iRaspberry is also available on Android (Android Market: iRaspberry Lite [free], iRaspberry Pro [AU$1.00]).



Friday, March 4, 2011

git sharing with dropbox

I recently posted about using Dropbox as a git server: git push dropbox.  I have since refined this process as I became more familiar with git and fine tuned my development workflow. So here's an update on how I now use Dropbox to both share and backup my git repositories.


(note: in the examples below "$" is the bash prompt; all commands are entered in the OS X Terminal. I'm very much an old school UNIX command-line guy! However, on other platforms, all commands will be possible from your favourite git client.)

Initialise a git repository for Project, if one does not already exist:


  $ cd Project
  $ git init .
  $ git commit


Create a git server repository in Dropbox.  It is initialised as "bare" which means it won't hold a working copy of the repository. It is a shared repository only.
  
  $ mkdir ~/Dropbox/git-server/Project.git
  $ git init --bare  ~/Dropbox/git-server/Project.git

In the local repository, add the Dropbox repository as the remote origin then push the current branch up to it.

  $ git remote add origin ~/Dropbox/git-server/Project.git
  $ git push origin master

That's all there is to sharing and backing up your project repository.  Whenever you need to (I generally do it after every commit) you can "git push origin master" again to update the Dropbox repository.

On another computer, to clone a copy of the project repository from Dropbox:

  $ git clone ~/Dropbox/git-server/Project.git
  $ cd Project
  $ git status

To update a clone from the Dropbox server:


  $ git pull origin master


Too easy!

Some other useful commands for examining the remote repositories:

  $ git remote
  $ git remote show origin


And note that you can add other remote repositories.  So you could add a github repository so you can selectively push stable snapshots of your project to github for public consumption, while keeping work-in-progress commits stored locally and backed up in Dropbox.

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.