Quick Hits: Unit Testing iPhone Apps

I have a few things to add to the woefully incomplete official documentation on setting up automated tests in your iPhone apps:

  1. You need to add your main application executable target as a direct dependency of the test target, so that you’re always testing against your latest build. Do this by double-clicking on the test target, going to the “General” pane in the properties dialog, and adding your app under “Direct Dependencies”. (This was actually mentioned in the OCUnit tutorial for Cocoa, but not the one about iPhone testing.)
  2. Your linker needs to know about the objects you’re testing. An easy way to do this is to add the .m files for those classes to the “Compile Sources” group in your test target. You’ll also have to make sure you link against any frameworks used by these objects. (You could also tell your app target to export all symbols, then link your test target to it as you would a library.) (Thanks to Chris Hanson for pointing out this procedural improvement in comments.) You need to explicitly link the object files of the classes you’re testing. These are the “.o” files in your build folder. To do this: Double-click on the test target, go to the “General” pane, add a new item under “Linked Libraries”. In the dialog that pops up, choose “Add Other…” and add your class’s .o file.
  3. When you run your tests, one failure looks like two: Failed tests show up in Xcode as errors (just like compile errors, &c). Any test failure triggers a second error, and you’ll see something like “Failed tests for architecture ‘i386′ (GC OFF)”. The docs never say so, but this appears to be normal. Fix the failing test, and it goes away.

Anything else to add? Drop us a comment!

Share
Posted in iPhone, Programming, Testing | Tagged , , , , | 8 Comments

UINavigationController Tricks

For an iPhone UI I’m developing, I need to have one UINavigationController nested inside another, and to have the inner UINavigationController’s events push a view on to the outer one’s stack. CocoaTouch didn’t give this to me for free, but there was a simple solution.

This post assumes that you’re familiar with the fundamentals of iPhone programming, including view controllers, UINavigationController, and delegates. There is sample code for this post, which is released under version 2 of the WTFPL.

The Problem

I’m working on a multi-step UI for a game. Expressing these steps as a regular drill-down table-style UI on the iPhone felt cumbersome, and games can’t afford for processes to feel cumbersome; people will stop playing. One solution that occurred to me was batching related sets of steps in a smaller navigation table – so in Step 1 you’d drill through substeps 1A, 1B, and 1C before moving to Step 2. The fact that the whole view wouldn’t be replaced with every choice seemed like it would be less destructive of the user’s mental context, and the chunking of substeps should make it easier for the users to wrap their heads around the process. (No word yet on whether this solves my UI problem, but I like it so far.)

My UI solution contained its own technical problem, though: If I’m expressing the process steps in a UINavigationController, and expressing the substeps in a nested UINavigationController, how does the inner navigation controller notify the outer one that the user’s last substep choice completes that step and it’s time to move to the next step – or in programmatic terms, how does the inner UINavigationController tell the outer one to push a new view onto the outer one’s stack?

Or, putting it more visually, how do I get from here:

navcontricks1

…to here:

navcontricks2

In the non-nested situation when you wanted to push a new view onto a UINavigationController’s stack, you’d do the following in the current view:

[self.navigationController pushViewController:nextViewController animated:YES];

So in my nested case, I need to do that, but pushing onto the outer navigation controller’s stack from a view controller on the inner navigation controller’s stack. I tried a number of naive (but sensible-seeming) targets for the pushViewController:animated: action, such as:

self.navigationController.navigationController
self.navigationController.parentViewController.navigationController

Nothing worked. I set a breakpoint and drilled down through the members of self.navigationController, and no path to that outer UINavigationController was apparent.

The Solution

While investigating the innards of UINavigationController, I stumbled upon a writable property that looked like it might help: The delegate. When creating the inner UINavigationController, I added one line of code:

innerNavCntlr.delegate = self; // THIS IS THE MAGIC, PART 1

Keep in mind that this is called in the view controller on top of the outer navigation controller’s stack, so “self” has access to the outer navigation controller.

When the view on top of my inner navigation controller’s stack is ready to signal the outer navigation controller, I do the following:

// THIS IS THE MAGIC PART 2
UIViewController *topVC = (UIViewController *)self.navigationController.delegate;
[topVC.navigationController pushViewController:nextCntlr animated:YES];

A couple of notes: First, the view controller that creates the inner UINavigationController must implement UINavigationControllerDelegate, or you’ll get a compiler warning – but you can just declare that it does so in the header file, as none of the methods in that interface are required.

Secondly: If you’re like me, this feels like an abuse of the delegate property. Now there’s no reason that you couldn’t actually use that object productively as the inner navigation controller’s delegate – I just haven’t done so here. And the fact that the delegate property has to be casted to be used this way says to me that it wasn’t meant for this – explicit casts are always a code smell. (Anal-retentive types might want to put in some type-checking around that cast for safety.)

That said, it works, and I haven’t run into a maintainability problem yet, as this code doesn’t really get re-used in many places. Were I expecting to use this trick often, I might package up a subclass of UINavigationController (call it NestedNavigationController) that actually took an outer UIViewController or UINavigationController property. Then again I’m finding that in Cocoa, subclassing is often a code smell…

Got a better solution? I’m interested – please leave a comment below!

Update: Whoops. Comments are enabled now.

Share
Posted in iPhone, Programming | Tagged , , , , | 7 Comments

iPhone OS 3.0 GM Seed installation steps via iTunes & XCode

I couldn’t find these instructions in the iPhone Development portal, so I figured i’d share them with you here.  Once you download the appropriate GM seed, here’s how it’s done (I’m assuming you’ve already designated your phone as a dev device.):

  1. Make sure…
    1. to downloaded correct GM Seed for your iPhone (1st Gen or 3G).
    2. that your system and version of iTunes meet the requirements: 

      “You must be running Mac OS X v10.5.7 and iTunes 8.2 in order to install iPhone SDK 3.0 GM Seed and iPhone OS 3.0 GM Seed.”

      “You must be running Mac OS X Snow Leopard Final Developer Preview (Build 10A380) and iTunes 8.2 in order to install the iPhone SDK 3.0 GM Seed for Snow Leopard and iPhone OS 3.0 GM Seed.”

  2. Extract the firmware .ipsw to a folder.
  3. Connect the iPhone to your computer.
  4. Back up your phone!
  5. Open iTunes and click Restore while holding the Shift key (for Windows) or Option key (for Mac).
  6. Locate or browse to the firmware IPSW.
  7. Wait for iTunes to unpackage and install the firmware.

OR you can just use Xcode…

  1. Back up your phone!
  2. Open Xcode
  3. Open the Organizer (Window -> Organizer)
  4. Flip the software version to “Choose…”
  5. Select the GM Seed ipsw file
  6. You should  see a message that says something like “These changes will take effect when you restore you phone”
  7. Click on the “Restore iPhone” button and watch the magic happen.

By backing up your phone ahead of time, you’ll be able to tell iTunes to put all of your crap back on it when the process is done.  It’ll even try to preserve icon locations :)

Happy hacking.

Share
Posted in iPhone | Tagged | Leave a comment

Quick Hits:Deploying iPhone projects to your iPhone/iPod Touch

This process sucks. Period. To get you through the suckage, I found this article with clear and useful steps. If you’re becoming an iPhone/iPod Touch developer, this is something you NEED to read. The article presents the steps of the deployment process in the best order possible to improve your chances at a successful push.

Deploying iPhone Apps to Real Devices

Happy hacking!

Share
Posted in iPhone, Quick Hits, Uncategorized | Tagged , , | Leave a comment

Quick Hits: A great FAQ for iPhone game developers

I came across this video from Brian Greenstone, the president and CEO of Pangea Software.  You might remember him from such iPhone games as Enigmo, Cro-Mag Rally, or Bugdom 2.

He’s made a video answering “10,000 ft view” questions about iPhone game development that people who are new to the platform should hear.  What he’s saying isn’t revolutionary, but it’s a lot of very good advice in 1 spot.

Enjoy and happy hacking!

Share
Posted in iPhone, Quick Hits | Tagged , | Leave a comment

Quick Hits: Things to keep in mind

(1) Before you unpack your gems, navigate to your vendors directory:

/Users/[yourname]/[your-app]/vendor/gems: gem unpack [gem name]

It isn’t the end of the world if you don’t do it ahead of time, but it does make life slightly easier.

(2) Remember that when you find a bug, you should write a test for the expected behavior BEFORE fixing the bug.  “The Rails Way” has a great quote about testing:

“It’s not that Rails encourages you to do test-driven development, it’s that it makes it difficult for you not to do test-driven development.” ~Brian Eng

I’ve interpreted this quote into the following mantra:

“It’s not that rails makes it easy to test, it that it makes you pay when you don’t”.

Finding a bug after creating your test suite just means that you missed a test.  Take the opportunity to correct that.

Happy hacking.

Share
Posted in Quick Hits, Rails, Uncategorized | 1 Comment

Google Maps events in Mobile Safari and PhoneGap for iPhone

Having trouble getting your Google Maps div to respond to events like you want in Mobile Safari or a PhoneGap app for iPhone? So was I. Disabling pinch-zoom is simple enough, but getting a finger drag to (a) not move the map and (b) draw something on the map was a major pain. I share my solution below: Proxying events through a transparent div overlaid on the Google Maps div.

To play along at home, you’ll need your own Google Maps key or you can just aim your mobile browser to my example code at http://kickasslabs.com/examples/gmap_events.html. (Note that this will not work in a regular desktop browser.) Besides a <script> tag importing the GMaps JavaScript, you should not need any other libraries or tools besides Mobile Safari to view and use the example page. If the page is zoomed way out when you start, just double-tap the map tile in the upper-left corner – it should zoom to fill your viewport.

What you should see is a map tile with a line painted on it. If you touch the screen, you should see one end of the line follow your finger.

It looks simple but I had a bit of trouble getting it to work, because touch events are not like mouse events (curious as to why?), and GMaps doesn’t respond to touch events by default.

Step by Step

First, you need 2 divs:

    <!-- This div holds our map -->
    <div id="map" style="width: 320; height: 320px"></div>
 
    <!-- This div lies on top of the map and acts as an event proxy -->
    <div id="mapoverlay" style="height:320px; width: 320px; position: absolute;"></div>

And you need to position one div over the other:

    mapDiv = document.getElementById('map');
    mapOverlayDiv = document.getElementById('mapoverlay');
    mapOverlayDiv.style.top = (mapDiv.offsetTop + 0) + 'px';
    mapOverlayDiv.style.left = (mapDiv.offsetLeft + 0) + 'px';

You set up the map div like you would for any other GMaps-enabled page:

    // the map
    _map = new GMap2(document.getElementById("map"));
 
    // coordinates for home base
    _lat = 37;
    _lng = -95;
    mapCenter = new GLatLng(_lat, _lng);
    _map.setCenter(mapCenter, 15);

Set up the overlay div to respond to touch events by firing a custom event for your map, e.g.:

    mapOverlayDiv.ontouchstart = function(e) {
      GEvent.trigger(_map, 'customTouchStart',
        (e.touches[0].pageX - mapDiv.offsetLeft),
        (e.touches[0].pageY - mapDiv.offsetTop));
    }

Now have your map respond to that event:

    GEvent.addListener(_map, 'customTouchStart', mapTouchStart);

…where mapTouchStart is a callback function that does something useful in response to a touch:

    function mapTouchStart(xPixel, yPixel) {
      redrawLine(xPixel, yPixel);
    }

And… well, then you’re done. My example also responds to the touchMove event and has a little code for drawing lines, but you’ve seen all you need to know to get event proxying up and running for your app.

Got a question? Got working code for an easier way? Drop me a line in the comments!

Share
Posted in Google Maps, hacks, HTML, iPhone, Javascript, Programming | Tagged , , , , | 4 Comments

Rails Gotcha: ActiveRecord Caches Associated Records by Default

ActiveRecord will cache the results of association method calls by default, unless you tell it not to.

(This applies to Rails 2.3.2 and perhaps earlier versions.)

From the documentation:

project.milestones             # fetches milestones from the database
project.milestones.size        # uses the milestone cache
project.milestones.empty?      # uses the milestone cache
project.milestones(true).size  # fetches milestones from the database

Normally, this is great. However, if you’re not aware of this default caching, you might see some strange behavior in your app or tests and have no idea what’s going on. This default caching behavior had me and Abel stumped until we read about it in the docs.

Working with the same concept as the example in the documentation, where a Project has many Milestones, here’s a more explicit example of the caching behavior in action:

project_1 = Project.find_by_id(1)  
project_2 = Project.find_by_id(1)  
# Load the same Project into two variables
 
project_1.milestones.length   
# - Hits the db's Milestones table.  
# - Caches milestones object on project_1.
# - Returns 0. 
 
Milestone.create(:project_id => 1, :name => 'New Milestone')  
# Adds a milestone to the project. 
# But we don't do it through the project_1.milestones association
# because that _would_ update project_1.milestones's cached value
 
project_1.milestones.length   
# Returns 0 (not 1, like you'd expect) 
# because project_1.milestones was cached when 
# previously requested above.
 
project_2.milestones.length  
# Returns 1, because project_2.milestones 
# hasn't been requested/cached yet.
 
# Note: project_2.milestones is 
# a COMPLETELY DIFFERENT IN-MEMORY OBJECT 
# than project_1.milestones.
# Taking this point further, here's an explicit example:
 
project_2.milestones << Milestone.create(:name => 'Another Milestone')
# Put another milestone on the project through 
# project_2's milestones association.
 
project_1.milestones.length
# Still returns 0, because project_1 already cached it's copy of 
# the milestones association back when there were 0.
 
project_2.milestones.length 
# Now returns 2, because only project_2 knows about the new milestone.
 
project_1.milestones(true).length  
# Returns 2, because ActiveRecord updates the cache 
# when association(true) is present.

Another funny something to note about the association caching behavior is that even when ActiveRecord uses a cached value, it still emits SQL to the log file. So, don’t let that trip you up either.

Share
Posted in Rails | Tagged , , , , | 2 Comments

Quick Hits: Setting the User Agent Header in Webrat

If you’ve read the new PragProg beta e-book on RSpec, you may have read that you can set HTTP headers for your Webrat request like so:

Given /^I am browsing the site using Safari$/ do
  header "User-Agent" , "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_6; en-us)"
end

Like me, you may have found out the hard way that this doesn’t work. Webrat does not automagically apply these new HTTP headers to your request – they certainly don’t make it to my controller. What worked for me:

Given /^I am browsing the site using Safari$/ do
  headers["User-Agent"] = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_6; en-us)"
end
 
When /^I visit my precious site$/ do
  get '/my/precious/path', my_query_string, headers
end

In the code above, headers is a method call that returns all the HTTP headers for your request. Just tack headers on as the third argument of your request, and you’re good to go.

Share
Posted in Rails, ruby, Testing | Tagged , , , , | 1 Comment

FasterCSV and noob-ish silliness

Here are two things I discovered this weekend:

Thing 1: FasterCSV is really cool!  It’s easy to use and does exactly what it should.  Here’s Scott Becker’s exporting tutorial.  For importing, i’ve hacked together bits from Peter Larkmund’s travels and the FasterCSV documentation.  Thanks to these tutorials, AMB will give users the ability to upload and export CSVs.

Thing 2: Default values for the win!  If you have a table that has a column for an amount, feel free to set the default value to 0.00.  Otherwise you won’t be able to do the following:

a = Account.new
a.amount += 20

…because a.amount will be nil.  You’ll throw an error and if you just updated a bunch of code, you might waste an hour trying to figure out why you’re throwing an exception.  However, if you declare a default value, you’ll be able to avoid any issues.

If you want me to go into further detail on either thing, just leave a comment.

Thanks for reading and happy hacking.

Share
Posted in Programming, Quick Hits, Rails | Tagged , | Leave a comment