ios

Flojuggler iOS Design Update

flo-explorationAs we move ahead with first round BETA testing I’ve started to explore the final aesthetic treatment of the brand, the details of the UI, and some ideas within the UX. Here we see the action buttons revealed after the user swipes downward on a list item. The item itself animates by sliding down, pushing the items below, and making a “click” sound on animation end.

The shadow is applied to the action buttons only. This makes them stand out against the dominating flat design of the item list. The conspicuous dimension of the shadow invites tactile investigation by the user.

The following images are direct grabs from the simulator. These are not mockups. Once the MVP is done I tend to do most of my refinement in live code. If the feature is drastic enough, like the control drawer pictured above, then I’ll mock it up first. That way I can get user feedback before investing in coding hours.

Flo List Screen

You can see how we start with a minimal viable product on the left and iterate toward a more refined UI/UX over sprints. The left facing arrows indicate the ability to swipe left which did not exist in previous versions.

Flo Detail Screen

As time passes we see the new logo, larger numbers, and larger thumbnails in response to usability during previous sprints,

Advertisements

Critiquing the Flojuggler Mobile App Icon

flojuggler-iconFlojuggler keeps track of menstrual cycles for the women you know. It’s not a complex application but the subject matter it deals with can be… sensitive to many. The original site was hugely successful with men and women alike. The majority of women using the site are tracking their own cycles. Now I’m tasked with moving the service from the web into a mobile application using new technology and new branding. At the doorstep of every mobile application’s brand is the app icon. It’s an app’s first and last impression with the user.

As we examine the choices made with the new Flojuggler icon, let’s start with the goals. Every app has some basic goals for an icon (see the Apple Human Interface Guidelines). It should be unique and stand out when alongside other apps. It should be easily related to the application’s function. An effective icon preserves clarity in small or large sizes. The basic droplet shape and primary red color fits all of these goals. Take a look at the home screen section in the icon set below. Even before the image is enlarged the red droplet pops out alongside the others.

icon

Then there’s conceptual goals for this application in particular. These are the fun ones. For this assignment we need a representation that is not too masculine nor too feminine since we have male and female target users. The use of primary red and white is quite unisex. We’re dealing with a very old subject on a very new platform so we don’t want to come off as “old fashioned”. We do not want to come off as ‘kitsch” but do want to come off as “fun”. This is a lifestyle application, not a medical one. We can hint toward the medical nature but don’t want to dwell in it. The simple shape, two tone color combo, and smoothness of the liquid reference execute all of those goals elegantly.

Saving and Deleting images via PhoneGap’s File API

The PhoneGap File API is a bit confusing to me and quite a few other people. I spent a fair amount of time looking for some “plain English” explanations and examples of storing images  but most tutorials and blog posts covered text files only. That’s great when you need to append or edit a data file but I needed to save images captured on an iPhone. It took a day of wrangling but I finally got my head wrapped around the basic API. Spent the next day building a set of methods. Let’s take a look.

I’m using PhoneGap (Cordova) 3.3 for this demo. It’s the latest stable build at the time of this writing. Seeing as PhoneGap will always be playing catch up with any changes to iOS it’s pretty important to check the docs and see if there’s been any major changes.

The Objective
We want to hit a button in the app and access the iOS photo gallery. From there we can navigate the gallery, edit a selected image, and then associate that image with a record. Pretty standard stuff for apps like Contacts, etc. This use case will need to use two of of the main API’s, Camera and File.

The Restrictions
When you call the Apple Photos App from your application the selected image file is stored in the host application’s tmp directory. This gives you a path to the file that looks something like this when you run the app in the simulator.

file:///Users/[name]/Library/Application%20Support/iPhone%20Simulator/7.0.3-64/Applications/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/tmp/cdv_photo_001.jpg.

Note the “tmp” part. That means that the file is now stored in a directory that is erased fairly often by iOS or by direct user action. If you have a reference to that file in a src attribute and the directory has been emptied to clear memory then the image breaks because it is nonexistent. When the user chooses an image we need to move (not copy) it to a persistent directory, grab the new URI, then store that new URI in the database. I say not copy because we’re looking to save drive space so it’s nice to move the file to a new location instead of creating a double.

The Code
The Phonegap best practice is to fire off your own code within the DOM ready event so we’ll start with that. When the event is fired it will log the event then run the requestFileSystem() method on line 13. The details are covered through comments.

// deviceready Event Handler
    //
    // The scope of 'this' is the event. In order to call the 'receivedEvent'
    // function, we must explicity call 'app.receivedEvent(...);'
    onDeviceReady: function() {
        app.receivedEvent('deviceready');
    },
    // Update DOM on a Received Event
    receivedEvent: function(id) {
        console.log('Received Event: ' + id);
        // request a new file system object then pass that to the gotFS() method.
        // gotFS() is a simple setter for a global var that the FileIO methods will use later.
        window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, FileIO.gotFS, FileIO.errorHandler);
    }

The second code snippet is directed to the Camera API and has three simple methods. Line 1 assigns a JQuery event handler to our button. In this case it’s been given a class of “.thumbnail-button”. When the DOM element is clicked it will execute the getPhoto() method defined on line 5. Passing the (pictureSource) parameter into the method makes the call more flexible. We’re using (pictureSource.PHOTOLIBRARY) to go directly to the iOS photo gallery and choose a photo. Had the objective been to take a new photo then we would pass (pictureSource.CAMERA) which opens the Apple Camera App instead of the Apple Photos App.

The getPhoto() method has a single call inside of it, navigator.camera.getPicture(), which takes three arguments; success callback, failure callback, and optional parameters.

I won’t get into the optional parameter set of the navigator.camera.getPicture() method because they should be fairly self explanatory. I would like to call attention to the allowEdit : true on line 11. This makes sure that we get a an image cropped to a perfect square by forcing the user into the edit mode after they tap on an image within the photo gallery and gives the user a chance to really zoom in on an area. Since we’re using these images for thumbnails we want square images with clear faces.

If the image is successfully saved the URI is passed to the onPhotoURISuccess() method on line 15 which passes that URI over to the FileIO portion of the task. We’re halfway done!


$(".thumbnail-button").on('click', function() {
getPhoto(pictureSource.PHOTOLIBRARY);
});

function getPhoto(source) {
    // Retrieve image file location from specified source
    navigator.camera.getPicture(onPhotoURISuccess, onFail, { quality: 50,
                                destinationType: destinationType.FILE_URI,
                                saveToPhotoAlbum: false,
                                sourceType: source,
                                allowEdit: true });
}

// Called when a photo is successfully retrieved
function onPhotoURISuccess(imageURI) {
    FileIO.updateCameraImages(imageURI);
}

// Called if something bad happens.
function onFail(message) {
    console.log('Failed because: ' + message);
}

Now we’ve got a new image sitting in the app’s tmp directory waiting for us to move it to a persistent location. We’ve taken the file URI of that image file and passed it over to another set of methods in the FIleIO name space. I’ve added comments to run through the sequence.

// set some globals
var gImageURI = '';
var gFileSystem = {};

var FileIO = {

// sets the filesystem to the global var gFileSystem
 gotFS : function(fileSystem) {
      gFileSystem = fileSystem;
 },

// pickup the URI from the Camera edit and assign it to the global var gImageURI
// create a filesystem object called a 'file entry' based on the image URI
// pass that file entry over to gotImageURI()
updateCameraImages : function(imageURI) {
        gImageURI = imageURI;
        window.resolveLocalFileSystemURI(imageURI, FileIO.gotImageURI, FileIO.errorHandler);
    },

// pickup the file entry, rename it, and move the file to the app's root directory.
// on success run the movedImageSuccess() method
 gotImageURI : function(fileEntry) {
       var newName = "thumbnail_" + gCurrentFlo + ".jpg";
       fileEntry.moveTo(gFileSystem.root, newName, FileIO.movedImageSuccess, FileIO.errorHandler);
 },

// send the full URI of the moved image to the updateImageSrc() method which does some DOM manipulation
 movedImageSuccess : function(fileEntry) {
      updateImageSrc(fileEntry.fullPath);
 },

// get a new file entry for the moved image when the user hits the delete button
// pass the file entry to removeFile()
 removeDeletedImage : function(imageURI){
      window.resolveLocalFileSystemURI(imageURI, FileIO.removeFile, FileIO.errorHandler);
 },

// delete the file
 removeFile : function(fileEntry){
      fileEntry.remove();
 },

// simple error handler
 errorHandler : function(e) {
       var msg = '';
       switch (e.code) {
       case FileError.QUOTA_EXCEEDED_ERR:
               msg = 'QUOTA_EXCEEDED_ERR';
               break;
        case FileError.NOT_FOUND_ERR:
               msg = 'NOT_FOUND_ERR';
               break;
        case FileError.SECURITY_ERR:
               msg = 'SECURITY_ERR';
               break;
        case FileError.INVALID_MODIFICATION_ERR:
               msg = 'INVALID_MODIFICATION_ERR';
               break;
        case FileError.INVALID_STATE_ERR:
               msg = 'INVALID_STATE_ERR';
               break;
        default:
               msg = e.code;
        break;
 };
       console.log('Error: ' + msg);
 }
}

IOS local notifications with Cordova v3+

Andrew Dahlman’s local notifications plug in is a life saver. He’s bridged the gap between the native Objective-C library and your standard JavaScript app sitting in the web view wrapper. I know that he submitted the plug in to the official Phonegap build but I’m not sure if it made it in. It should!

Like many open source projects, the original writer can’t stay on the project forever. When updates to dependencies occur software can become difficult to install/use. Such is the case with this amazingly useful code collection.

So I’m writing this post in order to document some of the issues I ran into along with some solutions. Feel free to comment, correct, or add to the show.

*Note: These are all Objective-C or Xml changes. The Javascript source gave me no issues at all.

1. The plist paradigm has been replaced with config.xml. The read me files refer to a plist file a few times and this can throw some folks off. There was at least one person on the GIT thread that mentioned this.

With the switch to config.xml comes the XML syntax.

<feature name="LocalNotification">
 <param name="ios-package" value="LocalNotification" />
 </feature>

2. The Cordova 2.3.0+ implementation is was missing the badge feature that Andrew had made available in the original Phonegap version. If you find local notifications useful for your app then putting badge numbers on the app icon seems pretty clutch so I added it. Line 16 inserts the badge option while lines 18-19 are for the background and foreground callback features that I personally did not need and left them commented. 


-(void)addNotification:(CDVInvokedUrlCommand*)command {

 NSMutableDictionary *repeatDict = [[NSMutableDictionary alloc] init];
 [repeatDict setObject:[NSNumber numberWithInt:NSDayCalendarUnit ] forKey:@"daily" ];
 [repeatDict setObject:[NSNumber numberWithInt:NSWeekCalendarUnit ] forKey:@"weekly" ];
 [repeatDict setObject:[NSNumber numberWithInt:NSMonthCalendarUnit ] forKey:@"monthly" ];
 [repeatDict setObject:[NSNumber numberWithInt:NSYearCalendarUnit ] forKey:@"yearly" ];
 [repeatDict setObject:[NSNumber numberWithInt:0 ] forKey:@"" ];

 UILocalNotification* notif = [[UILocalNotification alloc] init];

double fireDate = [[command.arguments objectAtIndex:0] doubleValue];
 NSString *alertBody = [command.arguments objectAtIndex:1];
 NSNumber *repeatInterval = [command.arguments objectAtIndex:2];
 NSString *soundName = [command.arguments objectAtIndex:3];
 NSNumber *badge = [command.arguments objectAtIndex:4];
 NSString *notificationId = [command.arguments objectAtIndex:5];
// NSString *bg = [command.arguments objectAtIndex:6];
// NSString *fg = [command.arguments objectAtIndex:7];

 notif.alertBody = ([alertBody isEqualToString:@""])?nil:alertBody;
 notif.fireDate = [NSDate dateWithTimeIntervalSince1970:fireDate];
 notif.repeatInterval = [[repeatDict objectForKey: repeatInterval] intValue];
 notif.soundName = soundName;
 notif.timeZone = [NSTimeZone defaultTimeZone];

 NSDictionary *userDict = [NSDictionary dictionaryWithObjectsAndKeys:
 notificationId , @"notificationId",
 command.callbackId, @"callbackId",
 nil
 ];

 notif.userInfo = userDict;

 [[UIApplication sharedApplication] scheduleLocalNotification:notif];

}

3. For some reason the call to the application object in the actual badge call was not working so I had to call it using the sharedApplication syntax instead.

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
 {

 UIApplicationState state = [application applicationState];
 if (state == UIApplicationStateActive) {
 // WAS RUNNING
 NSLog(@"I was currently active Selino");

 NSString *notCB = [notification.userInfo objectForKey:@"foreground"];
 NSString *notID = [notification.userInfo objectForKey:@"notificationId"];

 NSString * jsCallBack = [NSString
 stringWithFormat:@"%@(%@)", notCB,notID];

 [self.viewController.webView stringByEvaluatingJavaScriptFromString:jsCallBack];

 //application.applicationIconBadgeNumber = 0;
 [UIApplication sharedApplication].applicationIconBadgeNumber = 0;
 }
 else {
 // WAS IN BG
 NSLog(@"I was in the background");

 NSString *notCB = [notification.userInfo objectForKey:@"background"];
 NSString *notID = [notification.userInfo objectForKey:@"notificationId"];

 NSString * jsCallBack = [NSString
 stringWithFormat:@"%@(%@)", notCB,notID];
 [self.viewController.webView stringByEvaluatingJavaScriptFromString:jsCallBack];

 //application.applicationIconBadgeNumber = 0;
 [UIApplication sharedApplication].applicationIconBadgeNumber = 0;
 }
 }

4. Badges were removed from the Cordova plugin properties altogether. I mentioned that earlier. So I added a badge call into the cancel method and added an incrementBadge method.

- (void)cancelAllNotifications:(CDVInvokedUrlCommand*)command {
[[UIApplication sharedApplication] cancelAllLocalNotifications];</pre>
[UIApplication sharedApplication].applicationIconBadgeNumber = 0;
}

- (void)incrementBadge:(NSNotification *)notification {
int num = [UIApplication sharedApplication].applicationIconBadgeNumber;
[UIApplication sharedApplication].applicationIconBadgeNumber = num + 1;
}

Flojuggler iOS App

Flojuggler Mobile UI Design

Flojuggler is the lifestyle menstrual tracking application designed to track the cycles of multiple persons. There are times when you want to know which of the women in your life are having their periods. See the illustration below.

I created the entire brand, site design, and programming which included  “Facebook style” image management, extensive AJAX feature sets, JQuery UI integration, all on a true MVC architecture. This was another opportunity to invent a service from the ground up.

flojuggler-problem

The original Flojuggler was built on the CakePHP framework. I was in love with PHP at the time. Cakephp, a Ruby on Rails knock off written in PHP, and JQuery UI had just come out. Had a lot of fun making this one years ago but the technology, UI/UX, and brand have become dated so it’s time to make an iPhone app. Let’s take a look at that process.

Balsamiq Mockups

Balsamiq Mockups provides a ready to use language for wireframes and makes it easy for non-designers to make edits.

Wireframes: First step is to create a rough idea of what the app will do. I prefer to use Balsamiq Mockups to create wireframes because it’s simple and does not distract from the task at hand. Here we have three basic screens; The flo list, flo edit/create form, and the prediction form. This allows us to dip into the initial conceptualization of the UI/UX which leads to the code class structure. I’ll spend at least a week going over multiple versions of these wireframes. It’s cheaper to explore and make mistakes here rather than in the development cycles. Solid wireframes provide design and engineering with a consensus on where they are both headed which is supposed to be a single destination.

Designs: During this phase I’m living in Adobe Illustrator, Photoshop, and mostly Fireworks. The latter allows amazing rapid prototyping and final comps as it provides a huge amount of object support when compared to Photoshop. Fireworks allows producers to approach a project similarly to the way that a good framework allows a developer to proceed with structure. Illustrator and Photoshop help a designer create art. Fireworks helps a producer to create assets. When you’re wearing all the hats it really helps to have the right tools on hand.

ios-simulatorscreensnapz002

The UI/UX progression over time. The initial MVP on the left and the results after some iterating on the far right.

Since this is a ‘prototype’ project there’s not a lot of custom design up front. I’m really looking to emulate the iOS 7 look and feel first. Get the app working in an acceptable UI/UX format. The time for exploring real designer ideas comes later.

Diagrams: The wireframes are the stepping stone into my UML diagrams. Again, it’s cheaper to plan on ‘paper’  instead of planning while coding. Sure, I’m always going to run into problems and run back to the drawing board but this is about minimizing how much that happens and how much damage occurs in the event. I’ve also found UML diagrams great for code reviews before any code is written. Having a more senior programmer look of my diagrams helps keep my mind open as I have not invested any hours in code. It’s easier to throw away a diagram than it is to throw away a day’s worth of code.

Flojuggler is remarkably simple when it comes to class structure. There’s really just one, two if you found the model class. The rest of the methods are behavioral and not really dealing with the data other than calling flos to display or delete.

Flojuggler iOS native version

Initially I wanted to see how far I could get in Objective-C in two months.

Coding: While I’m very familiar with Javascript, I wanted to try and do this in Objective-C for all the obvious benefits of using a native language on the platform. Off the bat I knew that JS and Obj-C are not very close in syntax or structure but I felt like it was worth trying. At the same time I didn’t want to dig myself into a six month learning curve so I decided to time box the experiment. Two months of Obj-C and let’s see how far we can get.

Two months later I had a working CRUD in place. I learned the general syntax of Obj-C and dug a bit into the X-Code data model paradigm which I found to be very SQL like. It looked promising but… that was a steep learning curve for a simple CRUD. The time box had ended and it was time to start over with a Phonegap implementation. Let’s see what I can do in the same amount of time using Phonegap, a Javascript framework in iOS.

Flojuggler iOSA month later I had a complete prototype with all the core features and a few bells. With little to no ramp up on the core language it was much easier to crank out a product. In fact, there was even time to evaluate Backbone and Angular along the way. Given the minimal nature of the application it didn’t appear necessary to use another framework on top of Phonegap and JQuery Mobile so I wrote everything by hand. It’s less extensible but the project won’t extend and the codebase is a fraction of what it would be with additional libraries.

There’s some additional libraries for camera functionality, notifications, etc but this is the basic prototype done in a month. Now I can go into design mode and start coming up with final concepts for the real brand aesthetic.