Friday, October 14, 2011

Phonegap Plugins - Part 2 Developing plugin classes

Background


In my last post I talked about how to write ad-hoc plugins. To be honest I have largely used the ad-hoc approach for my plugins for a number of reasons:
  • My app is largely a javascript app. My snippets of obj-c do not warrant a full OO design. They warrant and ad-hoc approach IMO.
  • Some of the tasks I perform don't warrant their own obj-c class.
  • Some of the tasks I perform are complex, largely because I am talking to other 3rd party obj-c APIs like openFeint. Getting the the code in my app to talk to the phonegap and openFeint APIs was not obvious to me through delegation. I ended using NSNotificationCenter to setup event listeners in my code that would trigger when the event was invoked. I was more comfortable with NSNotificaitonCenter as a javascript developer. I will talk about the use of NSNotificationCenter in part 3 of this series.
However developing a plugin that is an obj-c class is very nice because it is very portable to other apps and because you can easily publish it to github to gain some credibility for yourself and maybe some extra app sales. To be honest, I will probably end up going through my ad-hoc plugins to extract some more "class" plugins that I can reuse easily and publish.

Why I developed this plugin


So the plugin I am going to talk about today is available on github here for you to download. It's also a very practical and useful plugin IMO. The plugin renders a native obj-c scrim loader/ajax spinner (never sure exactly what to call them!). In obj-c parlance, the plugin renders a UIActivityIndicator to the screen (yet another name for this thing!). 

Hold on! you might say. This doesn't sound too useful. This is pretty simple and low intensity stuff, especially to javascript developers. And I agree with you. It is. So let's examine the reason why we are going the plugin route in this case. It is always important to ask the "why" with plugins, otherwise just develop in obj-c. 

First of all, the inspiration for the ajax spinner came from MegaJump. They use them everywhere. I had a lot of fade in/outs between scenes and just felt that an ajax spinner would look good.

Secondly, when the user is left waiting for the game to start up or an image/data to download you need to let them know that every thing is good. Users already know that an ajax spinner tells them everything is fine.

Finally, in my game I have alot of graphical rendering going on using webkit transition. Webkit transitions are good if there is only one of them rendering to screen at a time, but if there's a few of them going on then  they tend to stutter and look cheap. So if I use a webkit transition to render an ajax spinner then I am going to eat up resource that could be better used elsewhere. My solution was to use the UIActivityIndicator in obj-c which is low-resource.

The strategy for designing the plugin was to create an instance of the class, render, hide it and then destroy it. The plugin is not designed to allow for multiple instances to be created in the background and rendered as required. 

To the meat of the matter


So let's look at the .h file the scrimLoader class:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "PhoneGapCommand.h"

@interface ScrimLoader: PhoneGapCommand {
  UIView *scrimView;
  UIActivityIndicatorView *myIndicator;
}

@property(nonatomic, retain) UIView* scrimView;
@property(nonatomic, retain) UIActivityIndicatorView* myIndicator;

- (void) showScrimLoader:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
- (void) dismissScrimLoader:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;

@end

Any phonegap plugin must contain @interface MyClass: PhoneGapCommand. The class should obviously contain a UIActivityIndicatorView, but it also requires a UIView to render on to which we name scrimView in our class. Getting the setup right in your .h file is probably the hardest part of writing plugin classes and there are different approaches.

In the EmailComposer plugin class (available here) the .h file class is written as follows:

@interface EmailComposer : PhoneGapCommand <MFMailComposeViewControllerDelegate> {

}

A special type of view controller delegate (MFMailComposeViewControllerDelegate) is made available in obj-c. The developer can leverage this delegate to create instances of MFMailComposeView and does not need to declare properties directly in the interface file.

But let's get back to our scrimLoader class. Here it is in full:

#import "ScrimLoader.h"

@implementation ScrimLoader
@synthesize scrimView;
@synthesize myIndicator;

- (void) showScrimLoader:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options 
{
  UIApplication* app = [UIApplication sharedApplication]; 
  UIWindow* appWindow = [app keyWindow];
  self.scrimView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)] aut  orelease];
  self.scrimView.backgroundColor = [UIColor clearColor];
  [appWindow addSubview:self.scrimView];
  [appWindow makeKeyAndVisible];
  self.myIndicator = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge] autorelease];
  self.myIndicator.center = CGPointMake(160, 440);
  self.myIndicator.hidesWhenStopped = NO;
  [self.scrimView addSubview:self.myIndicator];
  [self.myIndicator startAnimating];
}

- (void) dismissScrimLoader:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options 
{
  [self.myIndicator stopAnimating];
  [self.myIndicator removeFromSuperview];
  [self.scrimView removeFromSuperview];
}
It's interesting to go through the two methods. Let's look at the showScrimLoader method. The purpose of this method is to show the scrimLoader. Well it's more than than. It's actually to create a scrimView instance and a myIndicator instance and then to show them both. Let's go through the code line by line.

UIApplication* app = [UIApplication sharedApplication]; 
UIWindow* appWindow = [app keyWindow];
app is a handle to the singleton object [UIApplication sharedApplication]. A singleton object is an object which can only have one instance. The handle to [UIApplication sharedApplication] is also globally available to all classes. This is a very handy thing to know since we can leverage this to get to other parts of the app from anywhere in the code. In this case I wanted to get to the appWindow. Why? So I can create my scrimView. In obj-c a UIView needs to be created in a UIWindow.

The next 4 lines of code now make alot of sense:

self.scrimView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)] autorelease];
self.scrimView.backgroundColor = [UIColor clearColor];
[appWindow addSubview:self.scrimView];
[appWindow makeKeyAndVisible];
We create a scrimView that has an origon of (0,0) and takes up the full iPhone screen. I like to use autorelease so that obj-c uses its own garbage collector to dispose of the object at its leisure (like javascript). You could also manage the gc yourself. The scrimView has clear color ie it will be transparent and by default does not listen for user gestures (something that I required for my app). Now that we have created the scrimView and styled it we add it as a subview of the appWindow and then make it visible to the user.

Now we largely apply the same process to the myIndicator instance:

self.myIndicator = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge] autorelease];
self.myIndicator.center = CGPointMake(160, 440);
self.myIndicator.hidesWhenStopped = NO;
[self.scrimView addSubview:self.myIndicator];
[self.myIndicator startAnimating];
We create the instance of myIndicator, style it (ie position it mid-lower screen), add it to the scrimView (remember it needs a UI view to render to) and activate it (ie get it to start spinning once rendered to screen).


From your javascript code you can call the ajax spinner into action as follows:

PhoneGap.exec('ScrimLoader.showScrimLoader');
The dismissScrimLoader method is relatively easy to understand:

[self.myIndicator stopAnimating];
[self.myIndicator removeFromSuperview];
[self.scrimView removeFromSuperview];
The order in which we remove the object instances is important. The superView of myIndicator is scrimView so can't obviously remove it before removing myIndicator.

Finally to sue this propery simply make to following call from javascript:

PhoneGap.exec('ScrimLoader.showScrimLoader');

If you want to explore further then a collection of good phonegap plugins is available here.

Hope you enjoyed. And a twitter follow, stumble, digg and/or Bubble Electric download (don't worry it's free) is always appreciated.

Best, Nige

No comments:

Post a Comment

Share