Popping modals all night in iOS

Poppin bottles in the ice, like a blizzard
When we drink we do it right, gettin’ slizard
Sippin sizzurp in my ride, like Three 6
Now I’m feelin so fly like a G6
Like a G6, Like a G6
Now I’m feelin so fly like a G6

I recently discovered a personal knowledge gap in using Scroll Views, Navigation Controllers and modal windows to cleanly work together. Here’s a brief description:

Picture a deck of cards spread out in one long row of 52 cards, edged right up next to each other.  Picture your phone being able to show only one card at a time.  The user uses a modal view to select the next card to display.  When the card is selected, the modal is dismissed and that card is displayed.

Brief recap:

  • 9 hearts is displayed
  • you use modal view to select 3 spades
  • 3 spades is displayed

(Really brief ScrollView primer)  In iOS land, the phone screen is the ScrollView’s current frame, and the position of the cards are matched with the ScrollView’s current offset.  Imagine the ScrollView’s frame glides, or scrolls, across all 52 cards in either direction in an instant.

The problem I discovered happened when I was dismissing a modal view.  The presenter of the modal dismisses it and then messages its parent to scroll away from it and display the 3 spades.  What would happen is the scroll would display that section but those pages or ‘cards’ had not yet been built.  The offset was being set before the content was created.  For whatever reason, the ViewWillAppear method did not seem to be called for the newly selected card views.

This was difficult to debug because it wasn’t a simple error message I could search for, but more required a deeper understanding of the platform.  And it turns out the solution is quite elegant.

The dismissViewControllerAnimated call has a completion block parameter.  Perfect.  Put the messaging to the parent after we have dismissed the modal.

- (void)selectCard:(NSString *)cardNumber {
    [self dismissViewControllerAnimated:YES completion:^{
        [[self delegate] displayCard:cardNumber];
    }];
}

Worked like a charm.

iOS – Updating application state from a UITableViewCell

One of my current clients is a new restaurant where they want an iOS app from which their customers can order their food.  One of the requirements is allowing the user to select 0-n number of dishes for multiple dishes.  For example:

Side dishes

So the question is as the user is incrementing the values, where do we store this state?  The first thought is to just keep it in the UITableViewCell but this has major drawbacks, including:

  • the data is lost once the UITableViewCell is scrolled off screen, as it is re-used to display other dishes.
  • maintaining state in the view is just not clean design.  This is controller territory.

Protocols and Delegation

The sensible place to keep the data is in the controller.  Then the question becomes, “How do we call back to the controller from the UITableViewCell?”  

Using Protocols and Delegation, we can easily have the controller maintain the state.  Here’s I did this.

SideDishTableViewCell -> UITableViewCell

I subclassed the view cell with SideDishTableViewCell (SDTVC).  In SDTVC, I defined a protocol:

// SideDishTableViewCell.h

@protocol SideDishTableViewCellDelegate;
@interface SideDishTableViewCell : UITableViewCell

// define a @property to hold a reference to the delegate
@property (assign, nonatomic) id <SideDishTableViewCellDelegate> delegate;
@end

// define the @protocol here
@protocol SideDishTableViewCellDelegate <NSObject>
@optional
- (int)addItemWithCell:(SideDishTableViewCell *)cell;
- (int)removeItemWithCell:(SideDishTableViewCell *)cell;
@end

Now the SDTVC has a reference to a delegate which we’ll call whenever the user adds or removes items.  One note: I chose to pass the entire SDTVC because it contains bits of data that the controller needs. Another option is to just pass the dish’s ID and have the controller do a bit more work to get its metadata.

Calling the delegate from IBAction

Each of the + and – buttons are tied to IBActions, and the IBAction methods are where we call out to the delegate.

// SideDishTableViewCell.m

// The value 'q' is returned from the controller and is used to update the quantity displayed.  The View does no math.
- (IBAction)increaseQuantity:(id)sender {
    int q = [[self delegate] addItemWithCell:self];
    self.quantityLabel.text = [NSString stringWithFormat:@"%d", q];
}
- (IBAction)decreaseQuantity:(id)sender {
    int q = [[self delegate] removeItemWithCell:self];
    self.quantityLabel.text = [NSString stringWithFormat:@"%d", q];
}

Implementing the protocol from the View Controller

// SideDishViewController.h

#import "SideDishTableViewCell.h"
@interface SideDishViewController : UIViewController <SideDishTableViewCellDelegate>

And the implementation.

// SideDishViewController.m
-(int)addItemWithCell:(SideDishTableViewCell *)cell {
     // Update a collection that holds the dishes and their quantities

     // Return this side dish's quantity
}

Summary

There you have it.  We’ve kept the view very simple to the point it doesn’t even have to do any math.  It simply lets the controller handle the increment/decrement and then just waits for the controller to tell it what the new quantity is.

 

Welcome to the Inner Circle

Inner Circle is a simple game of predicting outcomes of events and comparing yourself against your friends.  I’ve started learning iOS dev and I am building Inner Circle to help me hone my iOS skills.  I have recruited 3 additional players and we just wrapped up the first week.  All questions were NFL selections.  Results are in the pictures below.

I decided to build Inner Circle to give me an opportunity to practice across several areas of iOS development.  This includes:

  • Getting and posting JSON to a server
  • Customizing the Table View to make the question selection cells bow to my command 
  • Using a WebView to view content from the web (this is the tables view)
  • Saving data locally between app restarts (username)
  • App icons – just anything besides the default!
  • And to see if we can make this game fun enough for us to want to continue playing

I’ve also coddled together a simple node.js app to manage all the data.  Feels good to work with Coffeescript again.  So clean!  And Heroku and Mongolab again to the quick and dirty rescue.

And big kudos to Test Flight for making it simple to share builds with your users.  All for free.

Next on the product timeline:

  • Adding the notion of Quizzes, or a grouping of questions
  • This will allow you to work on multiple simultaneous sets of questions
  • Display a selection list of the current open Quizzes

Make your picks

Make your picks

The scores after the first week.

The scores after the first week.

Moving Hopscotch to v3.0

“…to market and promote,
and you better hope
(For what?)
that the product is dope.” – A Tribe Called Quest

What’s happening in Hopscotch 3.0

There comes a time in every product’s lifetime that it needs a major reworking.  Hopscotch.fm has been live for over a year now and I’m happy with where it’s gotten to.  After not working on it much since early 2013, I decided it’s time to give it some love.  I brainstormed what I want to change.  Here are the notes:

Brainstorming notes

Brainstorming notes

The main points I want to focus on in HS 3.0 are:

  • Decouple the music listening from the browsing.  Right now reading about a show auto-plays songs from that show.
  • Use a front end MVC framework.  Leaning strongly towards AngularJS.
  • Responsive UI – the site doesn’t look amazing on a mobile browser.  Good timing as Bootstrap 3 purports to be a mobile first framework now.  Nice.

The layout concept

Looking at the sketch in my notes above, here is the layout I am going to work with.  This screenshot was done using Bootstrap 3.

Overall layout

Overall layout

The layout in code

This is the Jade template using Bootstrap 3:

	
div.container.fullheight

	div.row(style="border:2px")
		div.col-md-8
			h3 Header toolbar
		div.col-md-2
			h3 Social
		div.col-md-2
			h3 City

	div#radio.row.show-grid
		div.col-md-8
			h3 Radio player
		div.col-md-4
			h3 (unused space)

	div#lower.row.show-grid.fullheight
		div#artist.col-md-8.fullheight
			h3 Artist Detail
		div#shows.col-md-4.fullheight
			h3 Upcoming shows

This is the CSS in addition to Bootstrap’s:

div {
	box-shadow: 2px 5px 5px #888888;
}

.fullheight {
	height: 100%;
}

html,body { 
	height:100%; 
}

What’s next

I still want to focus on layout and building the framework to start using AngularJS.  As more concrete objectives, these would be:

  • set up AngularJS.  Create Controller etc and have it load today’s list of shows automatically on page load.  Once this is working, it should be smooth(er) sailing to get fancy.
  • mobile responsive css.  Buzzwords.  Want to get the site looking GREAT on mobile browsers.  Eventually I’d like to get it playing as a continuous radio station on mobile, which I believe can be done with SoundCloud.

Getting to wireframing

I have always believed wireframing tools were too rigid to let you be creative.  And so I’ve always been a pen and paper sketcher and I like the drawing part of it.  It’s fun and it can be very creative.

But where it doesn’t help is in when you’re trying to design to a higher level of precision.  I have started using Balsamiq and I was impressed with how limitless and simple it is.  Everything works as you’d expect it to, and in an hour I had a much clearer idea of what the ui I had sketched out could look like.  Things like portions and relative size to other objects on the page are much easier to see.

My sketches.  Chris Isaak is in my test group.

My sketches. Chris Isaak is in my test group.

Getting hopscotch.fm to version 1.0

This week I launched hopscotch.fm.  It is still a very early stage site and there are many things I’d like to add on and improve, but I decided I needed to draw a line in the sand and just ship something.  Anything.

In this blog post I want to give a general sense of how I went from idea to v1.0 going live.

Step 1: Proof of Concept

In this phase I wanted to rule out any potential major unknowns.  The two biggest ones were:

  • could I use Songkick’s API for my show listings
  • could I build a radio player around Youtube videos

The first one was answered fairly quickly.  The Songkick API features loads of shows and venues and while it’s not the cleanest data (dates are not always proper), it is a great start.

The second was a bit more involved.  I want to make hopscotch.fm a radio player so that when a song completes, it automatically moves on to the next song without the user doing anything.  I found a great library tubeplayer.js that did this.

Step 2: Get an ugly site up and running

It doesn’t have to be ugly, but I wasted no time in making anything look good.  I had buttons all over the page, images showing up over controls and other oddness.  But I got the functionality implemented.  The user hits play and:

  • The Youtube video starts playing
  • An artist image is downloaded via Youtube’s API
  • Venue info is retrieved from Songkick
  • Artist info is retrieved from Wikipedia.  I since removed this as it was the wrong half the time.

Step 3: Decide what constitutes v1.0 and do it

It’s probably no surprise that this step took the most time as now I needed to start caring about error handling and CSS and data validation and everything else that makes a real site, real.  I added Bootstrap, built a little admin page for syncing hopscotch data with Songkick.  Made a best guess effort at what a clean, simple user experience could be and from feedback I’ve gotten, I think it’s a decent start.

Step 4: Hosting and stuff

I originally put it on heroku as they have a free starter level.  But the 20 second startup time just to get the page to load is ridiculous so I’ve now switched to nodejitsu.  Data is on mongolab.  It’s all in the cloud.  It’s all happy.

Step 5: Go see a show!

That’s what hopscotch.fm is for so why not?

Mongo queries from node.js

You have a Mongo collection that you want to run a standard find on. Create a generic query worker (even the name isn’t exciting) to do the hard work while you simply pass in the name of the collection, the query to run and some optional options if you’re looking for more excitement. First I’ll show the query code then following that is an example of using it.

//Generic worker function to run a find query
exports.runQuery = function(myCollection, query, options, nextFn) {

    // perform the {query} on the collection and invoke the nextFn when done
    var db = new Db(dbname, new Server(host, port, {}), {native_parser:false});
    //console.log("host is " + host);
    db.open(function(err, db) {
        //console.log("reading mongo collection " + myCollection.toString());
        db.collection(myCollection, function(err, collection) {

            var optionsArray = {};
            if (typeof(options) != 'undefined') {
                optionsArray = options;
            } else {
                optionsArray['limit'] = 100;
                optionsArray['sort'] = {};
                optionsArray['sort']['_id']= -1;
            }

            optionsArray['slaveOk'] = true;

            collection.find(query, optionsArray, function (err, cursor){
                if (err) {
                    console.log("error is: " + err);
                }
                cursor.toArray(function(err, docs) {
                    console.log("found " + docs.length + " documents in " + myCollection);
                    var queryResults = [];
                    for(var i=0; i<docs.length; i++) {
                        queryResults[queryResults.length] = docs[i];
                    }
                    db.close();
                    nextFn(queryResults);
                });
            });
        });
    });
}

Calling the Query

Here we want up to 300 applications whose appId is neither sweetAppId1 or sweeterAppId2.  In the optionArray we ask nicely that it sorts them descending on the _id so we get most recent 300.

 queryParams['appId'] = {};
 queryParams['appId']['$nin'] = {};
 queryParams['appId']['$nin'] = ['sweetAppId1', 'sweeterAppId2'];

 var optionsArray = {};
 optionsArray['slaveOk'] = true;
 optionsArray['limit'] = 300;
 optionsArray['sort'] = {};
 optionsArray['sort']['_id']= -1;

 this.runQuery('appUsage', queryParams, optionsArray, function(results) {
      nextFn(results);
 });

Mongo grouping with node.js

Grouping in Mongo is equivalent to doing a GROUP BY in SQL. In this example we will be grouping within the Application collection. There are two bits of code here. First, it is the generic worker method that executes the grouping. It takes in all the parameters such as the key to group by and the reduce method to run and returns the results as JSON.

//Generic worker function to run a 'group by'
exports.runGroup = function(myCollection, inputKey, inputCond, inputInitial, inputReduce, options, nextFn) {

    // perform the {query} on the collection and invoke the nextFn when done
    var db = new Db(dbname, new Server(host, port, {}), {native_parser:false});

    db.open(function (error, client) {
      if (error) throw error;

      db.collection(myCollection, function(err, collection) {
          collection.group(
              inputKey, // key
              inputCond, // cond
              inputInitial,  // initial
              inputReduce, // reduce function
              true, // use the group command
              function(err, results){ 
                  nextFn(results);
              }
         );  // end of collection.group
      }); //end of db.collection
    });  //end of db.open
}

Second, we call the method on the Application collection to group by appId.

	var inputKey = { "appId" : true} ;
	var inputCond = { "timestamp" : { $gte: req.fromDate, $lte: req.toDate } };
	var inputReduce = "function(obj, prev) { prev.count++; }";
	var inputInitial =  { "count" : 0};
	var optionsArray = {};
	optionsArray['slaveOk'] = true;
	optionsArray['limit'] = 300;
	optionsArray['sort'] = {};
	optionsArray['sort']['_id']= -1;

	this.runGroup('restCall', inputKey, inputCond, inputInitial, inputReduce, optionsArray, function(results) {

		results.sort(function(a,b) {
		    // this sorts the array using the total count of the 'count' column
    		    return ((a['count']  b['count']) ? -1 : 0));
		});

		nextFn(results);
	});

Continuous Integration ++

If you’re not doing continuous integration yet, start.  But not just for the reasons you already know about.  Do it because it’s a great way to rapidly get people testing with the latest code base.  We have a Cruise Control server set up that builds every couple of hours, with the option to force a build.  The thing we do that I want to talk about here is that we auto-deploy the latest build to a running Tomcat server.  So effectively, once a build is done:

  • we know whether the unit tests are passing
  • we know if there are any problems in the app not starting
  • we have a server that developers can try out something that was recently checked in
  • non-development folk have a quick way of seeing how that feature that was built this week looks without needing ops to update our test environment
We’re working with a vendor that is building a Chrome Extension for us that pings out api’s for pricing data.  We’re getting close to wrapping up and as always is the case, we need to make a few tweaks here and there.  If they report an error or request an additional field be passed in the JSON, we make the change, check it in, force a build and then 5 minutes later they can continue their work.  This has saved us quite a bit of time and it’s something you should consider in your own environment.
Do you have a similar set up that you use, or are there other things you do in your CI environment that are time savers?  I’d love to hear about them.