Feb 08 2010

Download and XML parsing with HotCocoa

I’ve been working on Rife, a Google Reader client, over the last few days and have been digging my way through some more HotCocoa mappings. I figured the best way to remember some of this stuff is to write it down so, the following will look at synchronous and asynchronous downloads and writing a XML parser in HotCocoa/MacRuby.

So, what are we creating you ask? Well, as I said, I’ve been playing with Google Reader APIs and we’re going to do a synchronous request to Google for an identifier token. Once we’ve successfully authenticated we’re going to make an asynchronous request for our unread items. We’ll then parse the resulting XML document and spit the titles out to the console.

As with any HotCocoa application the easiest way to get started is to have system setup the shell of our application. We’ll use the hotcocoa command to create our application which I’m calling titles.

titania:example dj2$ hotcocoa titles

In order to authenticate to Google we’re going to need your username and password. Since I’m going to do the output to the console for demonstration purposes I’ll use the main application window to show the fields for username and password and a save button.

require ‘rubygems’
require ‘hotcocoa’

class Application
  include HotCocoa
 
  def start
    application(:name => "Titles") do |app|
      app.delegate = self

      window(:frame => [100, 100, 200, 200], :title => "Titles") do |win|
        win.center
        win.will_close { exit }

        win << label(:text => "Username", :layout => {:start => false})
        win << @username_field = text_field(:layout => {:start => false, :expand => [:width]})

        win << label(:text => "Password", :layout => {:start => false})
        win << @password_field = secure_text_field(:layout => {:start => false, :expand => [:width]})

        win << save = button(:title => "save", :layout => {:start => true}) do |button|
          button.on_action { authenticate }
        end

        @username_field.setNextKeyView(@password_field)
        @password_field.setNextKeyView(save)
        save.setNextKeyView(@username_field)
      end
    end
  end

  def authenticate
    puts "DO AUTH #{@username_field.to_s} #{@password_field.to_s}"
  end
end

Application.new.start

If you run the application by executing macrake in the titles directory you should see the main application window. Typing something into the username and password fields and pressing save you should see something similar to the following in your terminal.

DO AUTH test test

With our framework setup let’s get to the interesting stuff. First up, authenticating so we can retrieve our identifier from Google. We’re going to make a synchronous request to retrieve the identifier and, if successful, call a method to start retrieving our reading list.

def authenticate
  username = @username_field.stringValue
  password = @password_field.stringValue

  query = "https://www.google.com/accounts/ClientLogin?" +
          "Email=#{CGI.escape(username)}&Passwd=#{CGI.escape(password.to_s)}" +
          "&source=HotCocoaExample&service=reader"

  url = NSURL.URLWithString(query)
  request = NSMutableURLRequest.requestWithURL(url)
  request.addValue("HotCocoaExample", forHTTPHeaderField:"source")
  request.addValue("2", forHTTPHeaderField:"GData-Version")

  response = Pointer.new("@")
  data = NSURLConnection.sendSynchronousRequest(request, returningResponse:response, error:nil)
  data = NSString.alloc.initWithData(data, encoding:NSUTF8StringEncoding)

  if data =~ /^SID=(.*)\n/
    @sid = $1

    retrieve_reading_list
  else
    raise Exception.new("Authentication failed with: #{data}")
  end
end

def retrieve_reading_list
  puts @sid
end

If you add the above to your application, and add require 'cgi', you should be able to run the program, put in your username and password and get a long line of characters spit out on the terminal. Those characters are your Google SID.

Let’s look a bit closer at what we’re doing in the authenticate method. We start by grabbing the stringValue for the username and password fields. Then, using these values, we build the query string needed for authentication. This query string is used to build a URL object by calling NSURL.URLWithString(query). With the URL in hand we can start building our request object. This is done by calling NSMutableURLRequest.requestWithURL(url). I’m using the mutable version of the request as I want to add a few extra header values. These are both added with addValue(value, forHTTPHeaderField:field).

When we execute our request the system is going to want to put our response object somewhere. In the Cocoa version the method accepts a NSURLResponse **response parameter. In order to handle the response we need to create a Pointer object which is a MacRuby object for handling these pointers to objects. We want our pointer to point to an object so we use Pointer.new("@").

With the response setup we call NSURLConnection.sendSynchronousRequest and provide our request and response objects. I don’t care about the error, but if you do, you’d want to pass in something similar to our response pointer. The request will return a NSData object which we convert to a string using the initWithData initialization method of NSString.

With the string in hand we try to extract our SID and, if successful, execute the retrieve_reading_list method which just spits out the SID.

OK, cool, we’ve now got our authentication token and are ready to move onto the asynchronous request to get our reading list.

def retrieve_reading_list
  query = "https://www.google.com/reader/atom/user/-/state/com.google/reading-list?" +
          "xt=user/-/state/com.google/read&ck=#{Time.now.to_i * 1000}&n=2"

  url = NSURL.URLWithString(query)
  request = NSMutableURLRequest.requestWithURL(url)
  request.addValue("HotCocoaExample", forHTTPHeaderField:"source")
  request.addValue("2", forHTTPHeaderField:"GData-Version")
  request.addValue("SID=#{@sid}", forHTTPHeaderField:"Cookie")

  NSURLConnection.connectionWithRequest(request, delegate:self)
end

def connectionDidFinishLoading(conn)
  puts NSString.alloc.initWithData(@receivedData, encoding:NSUTF8StringEncoding)
end

def connection(conn, didReceiveResponse:response)
  if response.statusCode != 200
    puts "BAD STATUS: #{response.statusCode}"
    p response.allHeaderFields
  end
end

def connection(conn, didReceiveData:data)
  @receivedData ||= NSMutableData.new
  @receivedData.appendData(data)
end

Similar to the synchronous method we start by building our query string, NSURL and NSMutableURLRequest. We’ve added a cookie to our request object to hold the SID retrieved earlier from Google.

We fire the request by calling NSURLConnection.connectionWithRequest(request, delegate:self). We specific ourselves as the delegate for the connection. There are a few delegate methods we can implement to receive data and get notified of request states. These are:

  • connectionDidFinishLoading(connection)
  • connection(connection, didReceiveResponse:response)
  • connection(connection, didReceiveData:data)

We’ll look at our implementation of each of these callbacks in turn. First, in connectionDidFinishLoading(conn) we’re just printing out the data retrieved. We need to convert the data, similar to what we did in the synchronous request, from a NSData object to a NSString object.

In connection(conn, didReceiveResponse:response) we’re just checking to see if we got a 200 response code from the server. In all other cases we print an error.

The main work is done in connection(conn, didReceiveData:data) where we create a NSMutableData object if needed and append any data received into the mutable data object.

Running the code at this point should dump the first two items in your reading list to the console. The data will be a big mess of XML but we’ll look at parsing that in the next step.

def connectionDidFinishLoading(conn)
  xml = HotCocoa.xml_parser(:data => @receivedData)
  @receivedData = nil

  xml.on_start_document { puts "Starting Parse" }
  xml.on_end_document do
    HotCocoa.notification(:post => true, :name => "all_entries_loaded", :object => nil, :info => nil)
  end
  xml.on_parse_error { |err| puts "Parse error #{err.inspect}" }

  xml.on_cdata { |cdata| @elem_text += cdata.to_s }
  xml.on_characters { |chars| @elem_text += chars.to_s }

  xml.on_start_element do |element, namespace, qualified_name, attributes|
    @elem_text =
  end

  xml.on_end_element do |element, namespace, qualified_name|
    puts @elem_text if element == ‘title’
  end

  xml.parse
end

We’re finally getting into some HotCocoa specific code with our XML parser. HotCocoa defines a mapping wrapper around NSXMLParser and provides a set of delegate methods. These delegates mean we don’t have to set our class as the delegate and create a bunch of methods. They mean we can attach our code as blocks on our XML object. All the better if you want to define a few parsers in one class.

We start off by creating a HotCocoa.xml_parser. The parser accepts NSData objects so we don’t need to convert our response data to a string. We then setup eight callbacks. There are actually a bunch more callbacks that can be hooked up and you should look at the xml_parser mapping code to see if you need any of them. For our purposes, we only really care about eight.

The on_start_document, on_end_document and on_parse_error callbacks, as you can probably guess, get called when we start parsing, when we finish parsing and when we receive a parse error, respectively. We don’t really care about start in this example, but I put it in anyway. When we’ve completed parsing we send a notification and other application code can then listen for this notification and do anything it needs. If we wanted we could store the entries as they’re parsed and provide them to the :object key. This would make those entries available to anyone that receives the notification.

If we receive either CDATA, with on_cdata, or text, with on_characters, we append the content to our current elements text. When we receive the open tag of a new element, on_start_element, we dump our current element text as we’ve started a new element. We can also take a look at the elements name, attributes, namespace and qualified name, if desired.

Finally, in on_end_element we print out the current element text if the element we’re finishing has a name of title.

With all the callbacks configured we use xml.parse to start the parser. You should, if you run this example, see the titles and authors of the first two posts in your reading list. (The author name is also called title and I’m not bothering to check that the parent element is entry before spitting it out.)

That’s it. You can now make synchronous and asynchronous requests for content and parse any resulting XML.

One last thing before you go. Both of the requests we did above were GET requests. You can do other types of requests using the same methods as above you just need a slightly different setup for the request. You can see a POST request below.

request = NSMutableURLRequest.requestWithURL(url)
request.addValue(SOURCE, forHTTPHeaderField:"source")
request.addValue("2", forHTTPHeaderField:"GData-Version")
request.addValue("SID=#{@sid}", forHTTPHeaderField:"Cookie")

body="first=1&second=2&third=3"

request.setHTTPMethod(‘POST’)
request.setValue(‘application/x-www-form-urlencoded’, forHTTPHeaderField:‘Content-Type’)
request.setValue(body.length.to_s, forHTTPHeaderField:‘Content-Length’)
request.setHTTPBody(body.dataUsingEncoding(NSASCIIStringEncoding))

The first few lines should look familiar from creating our asynchronous request above. Since we’re going to be posting the data we use setHTTPMethod('POST') to setup the request method. We’ve form encoded the data so we set the appropriate Content-Type and set the Content-Length. Note, we convert the length to a string before sending to setValue. Finally, we set the body of the post with setHTTPBody. You need to convert the body string into a NSData object which we do with the dataUsingEncoding method. If you don’t convert the body to NSData you’ll end up sending a nil body with your post request.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Jan 24 2010

On Gaming

Categories: Gaming
Tags:

I’m a gamer. My friends and I have spent a lot of time playing various table top RPG games such as Dungeons and Dragons and Mage. The amount we play has tapered off of the last few years as the group spread out, got married, had kids, became adults. We do still game, and some of those games have been running for years, other games have fallen by the wayside for various reasons.

Recently I’ve found myself thinking about gaming. I’ve never been good at the histrionics part of the game. I can find interesting ways to combine rules, feats and spells but describing the outcome is not a strong point. I’ve always felt awkward describing scenes and actions. I think this is the basis of my not running games. Much to the disappointment of my friends. In the last, 15 I think, years I’ve been gaming I’ve run twice. Once for a group and once as a solo. The group game was too long ago to remember how it went. I was told the solo was good, but it didn’t feel solid to me. Maybe I didn’t have a good enough grasp on where I wanted it to go, maybe it was something else.

The lack of description confidence is part of the reason I hang in the background in games. I’ll let the other players take the lead on quests, satisfy their personal agendas. I’ll tag along and do my bit, and sometimes come up with ideas that cause the GM to think, but don’t typically look to take the lead in games. This can, obviously, have a detrimental effect on character development. Especially after a character, played for 2-3 years, dies and you start anew. Developing the skeleton for the new character to hang off, while hanging in the shadows, is difficult.

Maybe this is an experience thing. Practice makes perfect and all that. I guess the question becomes, how to you get better at the descriptive aspects, the creative aspects, the design parts of the game?

How do you transition from a player in the background to a GM? Or, with smaller increments, a player that steps in the fore more often.

I know our main GM would love for more of the descriptive elements in the game. Hell, he’ll give XP rewards for descriptive write-ups of game content. Die modifiers if you give good descriptions of actions.

So, I ask you, gentle reader, how do you work on your descriptions, your histrionics, your character and world development? How do you make your game better?

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Oct 26 2009

Poking Objective-C with a Testing Stick

4012929364_54af446306_bI’ve wandered back into Objective-C coding land recently. After spending so much time doing Ruby work I’ve gotten used to writing unit tests and using mock objects. To that end, I spent a bit of time figuring out OCUnit (built into XCode) and OCMock. I figured I’d write some of this down so I don’t forget for the next time I try to set this all up.

Build Results 1We’re going to work with a simple Cocoa Application that connects to an external web resource, in this case, Google Reader authentication. We’ll setup the testing bundle to run when our main application is built and create a mock object so we don’t hit the external resource on every test run. I’m going to be using XCode version 3.2 and create a Cocoa Application called UnitTesting. If you build and run this application you should see an empty window on screen. If you bring up the Build Results window (Build -> Build Results) you’ll see some information about the current build.

Add Unit Test TargetWith our app setup, we’ll start integrating the unit tests. Click on the Targets item in the project folder. Select Add -> New Target. Select Unit Test Bundle and press Next (Note, make sure you’re in the Cocoa and not the Cocoa Touch section when selecting Unit Test Bundle). I named my target Unit Tests and hit Finish.

At this point if you right click on the UnitTesting target and select Build “UnitTesting” everything should work correctly. If you right click on Unit Tests and select Build “Unit Tests” you should receive a build failure.

Add Test GroupLet’s add some tests. The first step is to create a new group to hold our test files. Right click on the UnitTesting item in the project draw and select Add -> New Group. Name the new group Tests. Now, right click on the Tests group and select Add -> New File…. You’ll want to add an Objective-C test case class (Note, make sure you’re in the Cocoa Class section of the new dialog and not the Cocoa Touch Class section or you’ll get an iPhone test class). Name the test GReaderTest.m and de-select the Also create “GReaderTest.h” option. The reason for this is that the headers are usually empty so there is no point in creating the extra file. You’ll also want to make sure you have the Unit Tests target selected.Add Test Case

When working with OCUnit you need to name each of your test classes SomethingTest. The trailing Test is required. In a similar vein, each of the tests themselves needs to start with test. So, something like testAuthentication.

Since we didn’t create a header file we’ll need to setup the interface for the test in the .m file.

#import <SenTestingKit/SenTestingKit.h>

@interface GReaderTest : SenTestCase
@end

@implementation GReaderTest
@end

Bundle Build FailureYou should be able to build the Unit Tests target now and have the build succeed. If the build doesn’t succeed, and you see a message about UIKit then you selected the Cocoa Touch unit test bundle instead of the Cocoa unit test bundle.

Adding dependenciesWith the unit tests building we can add them into our main build as a dependency. Right click on the UnitTesting target in the project drawer and select Get Info. In the General section add a new Direct Dependency for the Unit Tests target.

Now, when you press apple-B to build the project you should see your unit tests executed before the main build phase.

Build Results 2Ok, with everything setup we can start testing. First step, each test in this set will be using our GReader object. So, we’ll add setUp and tearDown methods that will be executed before and after each test, respectively.

#import <SenTestingKit/SenTestingKit.h>
#import "GReader.h"

@interface GReaderTest : SenTestCase {
    GReader *gr;
}
@end

@implementation GReaderTest
- (void)setUp {
    gr = [[GReader alloc] init];
}

- (void)tearDown {
    [gr release];
}
@end

This, of course, won’t execute as we haven’t created our GReader object yet. Let’s do that now.

Right click on the Classes group and select Add -> New File…. Add a new Objective-C class which is a subclass of NSObject. Call this new class GReader. The new class should be attached to both our main target and the Unit Tests target.

Now, for our little app, the first thing we’ll need to do is authenticate with Google Reader. There is a really good document on the Reader API from the pyrfeed project. We’ll need to post some specific data to a given end point and parse the response.

For our unit tests, we don’t actually want to hit the Google endpoint. There is too much time involved and we want our unit tests to be fast. So, we’ll need to do some mocking in order to verify the call is happening, but not actually make the call itself.

For this we’ll use OCMock. I’m going to add the test first and then we’ll add the OCMock.framework into the project. First, we need to import OCMock. This is done by adding #import <OCMock.h> to the beginning of the GReaderTest.m file.

The test is defined as follows.

- (void)testAuthentication {
    id mock = [OCMockObject partialMockForObject:gr];
    [[[mock stub] andCall:@selector(fakeAuthenticationPost:)
                 onObject:self] post:[OCMArg any]];
   
    [gr authenticateWithUsername:@"dan" password:@"password"];
}

- (NSString *)fakeAuthenticationPost:(NSString *)request {
    NSArray *d = [request componentsSeparatedByString:@"&"];
   
    STAssertTrue([d containsObject:@"Email=dan"], @"Username not set correclty into request");
    STAssertTrue([d containsObject:@"Passwd=password"], @"Password not set correctly in request");
   
    return @"SID=mysid\nLSID=mylsid\nAuth=myauth";
}

As you can probably tell, we’ve added two methods. There is only one test, testAuthentication but we needed a helper function to deal with the mock post call. Let’s take a look at what’s going on in these methods. We know that the GReader object will be making a call that we want to mock. So, we need to create a partial mock object that sits around our GReader object. This is done as: id mock = [OCMockObject partialMockForObject:gr];. With the mock created, we can stub out specific methods of the GReader object, this is called Method Swizzling in Objective-C land.

In order to swizzle the method we have [[[mock stub] andCall:@selector(fakeAuthenticationPost:) onObject:self] post:[OCMArg any]];. So, we create a new stub on our mock object. We then tell the stub to call the fakeAuthenticationPost defined in the current object whenever the post method is called on the GReader object. Our post method takes one parameter so we specify [OCMArg any] to allow any argument through.

Finally, we call the authenticateWithUsername:password: on the GReader object to kick off the authentication.

The second method we defined, fakeAuthenticationPost, will be called instead of the post method in the GReader object. To that end, it will receive the same parameter, the NSString that is pass to post. To be on the safe side, I’m verifying that we’re properly passing the required email and password fields in the string. I then fake some return data that is similar to a successful response from Google.

If we try to build the project at this point we’re going to get a lot of errors. First, since we haven’t added the OCMock framework and second, we haven’t created the authenticate or post methods for our GReader object.

First things first, let’s get the OCMock framework setup. To do that, you’ll need to download OCMock. You can grab the .dmg file off the OCMock pages. When you extract the archive you’ll see the OCMock.framework and the source directories. In order to keep everything in Git, I created a Framework directory in my UnitTesting directory. I then copied the OCMock.framework directory into this new Framework directory.

You could also install OCMock in the /Library/Frameworks directory but I like having it in Git as not all the developers may have OCMock installed.

Back in XCode, we need to add the framework to our project. Right click on the Frameworks group in the project directory. Select Add -> New Group. Name the new group Testing Frameworks. Note, this isn’t required, I just like the separation it provides. Finally, right click on Testing Frameworks and select Add -> Existing Frameworks... then press the Add Other… button. Navigate to the OCMock.framework directory you just copied into Frameworks directory and press Add. Make sure the new framework is hooked up to the Unit Tests target. Right click on the framework and hit Get Info in the general tab you can verify the targets.

Building the project at this point will get some warnings about missing functions and a crash executing the unit tests. In order to fix the crash we need to setup the build to copy the OCMock framework into our build directory. I’m not entirely sure why this is needed, something about one of the paths set in the framework, but it does get things working.

Copy Build PhaseRight click on the Unit Tests target and select Add -> New Build Phase -> New Copy Files Build Phase.

You need to set the Destination to Absolute Path and the Full Path to $(BUILT_PRODUCTS_DIR). Once the Copy Files phase is created drag the OCMock.framework from the Test Frameworks group into the copy phase. The copy phase needs to be placed between Compile Sources and Link Binary With Library phases.

Built Products dirAt this point, we should be able to execute our build again and we’ll get a failure [NSProxy doesNotRecognizeSelector:post]. This is good. This means our mock is setup correctly and is trying to hook into our non-existant post method.

Let’s go create a couple methods so things compile correctly. First we update the GReader.h file as follows.

@interface GReader : NSObject
- (void)authenticateWithUsername:(NSString *)username password:(NSString *)password;
- (NSString *)post:(NSString *)request;
@end

And the implementation.

#import "GReader.h"
@implementation GReader
- (void)authenticateWithUsername:(NSString *)username password:(NSString *)password {
    [self post:[NSString stringWithFormat:@"Email=%@&Passwd=%@", username, password]];
}

- (NSString *)post:(NSString *)request {
    return @"My post data";
}
@end

With that in place our build should succeed. You can make sure that things are working correctly by modifying [gr authenticateWithUsername:@"dan" password:@"password"]; to something similar to [gr authenticateWithUsername:@"stan" password:@"password"]; and you should see a build failure.

That’s it. We have the build system setup and the mock objects hooked up. We can now continue down our TDD path. You can see an example project that I’ve uploaded to GitHub.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Oct 08 2009

What’s your moniker

Categories: Computers, Programming
Tags: ,

IMG_6929We were playing with some ideas over at PostRank and realized we needed a way to give out some unique codes. We didn’t want some random string of letters and numbers as that isn’t very memorable. We wanted real words. We wanted something entertaining.

I spent a bit of time looking around. The closest thing I found to what we wanted was Webster. While Webster would work, I wasn’t a big fan of the words we were getting out of the system in some quick tests. After some more fruitless searching, in a fit of not invented here syndrome, I created my own.

Enter, Moniker. Moniker will take a list of descriptive words and a set of animals and give you a string. There are, currently, just over 42 thousand combinations. Enough for what we needed. The system is pretty simplistic and it’s up to you to make sure you aren’t getting duplicates.

titania:~ dj2$ irb
>> require ‘rubygems’
>> require ‘moniker’
=> true
>> Moniker.name
=> "octagon-zebra"
>> Moniker.name
=> "shallow-lion"
>> Moniker.name
=> "concave-parrot"

The code is all up on GitHub so take a look and feel free to play. Let me know if you’ve got any ideas for improvements

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Aug 13 2009

Flicking the iPhone

IMG_4589I’ve been playing with my iPhone a bit lately. Trying to work out an application that I think will be useful (more on that some other time). In the mean time, I wanted to let the user of the application flick between views to see different detailed view screens. Each flick should take the user either forward or back in the list. The transitions should push the new item into view and have a little bounce on the end.

Turns out, this wasn’t trivial. I’m not sure now why I thought it would be trivial. Maybe because I’ve seen it in a few apps.

Anyway, what follows is how I did it. Maybe there is an easier way, if so, let me know, but this appears to work.

I’m going to create a simple Core Data backed, Navigation-based iPhone application. There will be two screens, the main list screen and a detailed view. The user will be able to flick between detailed views. Note, there is no real error checking, and possibly memory leaks in this code. You’ll need to fix up as appropriate for your application.

Firing up XCode, create a new project. Under the iPhone OS Applications template section, you’ll want to select Navigation-based Application. Make sure you’ve got Use Core Data for storage selected as well. In my case I’m naming the application Flick.choose_template

With the application created you should be able to execute it in the simulator and add a series of timestamps to the main view.

managd_object_classBefore we start adding screens, let’s create a class to hold our model entity. Double click on the Flick.xcdatamodel file to open the data modeler. Click on the Event entity and press command-n. Select, in the Mac OS X Cocoa section, Managed Object Class. Click through to finish the creation as the rest of the defaults will suffice. You should now have Event.h and Event.m files created in your project.create view controller

With the base application in place we still need to create a screen to allow viewing of our events. I’m backing this screen with a UITableViewController because that’s how my application does it. You could probably use a plain UIViewController if you don’t need to extra functionality of the table view controller.

Add a new file to the project and, under iPhone OS Cocoa Touch Class, select Objective-C class and set a Subclass of UITableViewController. Name the file EventViewController.m and have the .h file auto generated.

Opening up RootViewController.m we need to hookup the code to create our new view when one of the rows is selected.

First, lets mark the rows as having another view. Add cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; into the tableView:cellForRowAtIndexPath: method right after the UITableViewCell is created.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    Event *event = (Event *)[fetchedResultsController objectAtIndexPath:indexPath];

    EventViewController *controller = [[EventViewController alloc] initWithStyle:UITableViewStyleGrouped];
    controller.event = event;

    [self.navigationController pushViewController:controller animated:YES];
    [controller release];
}

Using the tableView:didSelectRowAtIndexPath: delegate method we retrieve the selected event from our fetchedResultsController. We then create a new EventViewController with a table style of UITableViewStyleGrouped. There are two styles to tables Grouped and Plain.

UITableViewStylePlain
A plain table view. Any section headers or footers are displayed as inline separators and float when the table view is scrolled.
UITableViewStyleGrouped
A table view whose sections present distinct groups of rows. The section headers and footers do not float.

With the controller created we assign the selected event to the controller. Finally, we push the new view controller into the navigation stack using [self.navigationController pushViewController:controller animated:YES]. This will set our new view as the view to be displayed. Oh, and don’t forget to import EventViewController.h and Event.h.

We want our new view controller to have a back button. If you don’t set the name in the MainWindow.xib file the back button will be available, but invisible. Double click the MainWindow.xib file to open it in Interface Builder. Click on the navigation bar in the view and press shift-command-i to open the inspector. Press command-1 to switch to the attributes panel. In the Back Button field enter Events. You can now save and exit Interface Builder. Thanks to Owen, setting self.navigationItem.title = @"Events" in the viewDidLoad method of RootViewController will set the correct text into the back button.

Continuing on, we can fill in the EventViewController files, starting with the header.

#import "Event.h"

@interface EventViewController : UITableViewController {
    Event *event;
}
@property (nonatomic, retain) Event *event;
@end

The view controller has one attribute, the event that was selected. We setup a property for the event to handle the getters and setters for us.

#import "EventViewController.h"

@implementation EventViewController

@synthesize event;

- (void)viewDidLoad {
    self.navigationItem.title = @"Event Info";
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 1;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *LocationCellIdentifier = @"Timestamp";
   
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:LocationCellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
                                       reuseIdentifier:LocationCellIdentifier] autorelease];
        cell.accessoryType = UITableViewCellAccessoryNone;
    }

    NSDateFormatter *dateFormat = [[[NSDateFormatter alloc] init] autorelease];
    [dateFormat setDateStyle:NSDateFormatterLongStyle];
    [dateFormat setTimeStyle:NSDateFormatterLongStyle];

    cell.textLabel.text = [dateFormat stringFromDate:event.timeStamp];
   
    return cell;
}

- (void)dealloc {
    [super dealloc];
}
@end

Next up is the implementation file. When the view finishes loading we want to set the title to display Event Info. This is done in viewDidLoad by calling self.navigationItem.title = @"Event Info".

We’re only going to show one thing in our table, the timestamp, so we setup the table to have a single section and a single row. We then create a cell to display the time stamp.

You should be able to build and run the application at this point. You can click on the events to move to the subview. From there you can navigate back to the main list.

With that, we’re ready to add some flicking to our application.

There is no flick event built into the iPhone APIs that I could find. The way to achieve a flick is to listen for the touchesEnded:withEvent: delegate method and determine if a flicked happened. The one catch, this delegate is available on UIView objects. We don’t, currently, have a UIView object we can attach the delegate method too.

So, we’re going to subclass UIView deal with the flicking, then let our parent EventViewController know when the flick happened so we can switch views. Sound good? Ok, let’s go.

Add a new class to the project. Under iPhone OS Cocoa Touch Class select Object-C class. Make sure UIView is selected in the drop down. Name the new file EventView.m.

@protocol EventViewDelegate;

@interface EventView : UITableView {
    id <UITableViewDelegate, EventViewDelegate> delegate;
}
@property (nonatomic, assign) id <UITableViewDelegate, EventViewDelegate> delegate;
@end

@protocol EventViewDelegate <NSObject>
- (void)eventView:(EventView *)view flickDirection:(int)dir;
@end

There are a couple things going on here. First, I switched the inherited class to UITableView as that’s what we’re actually dealing with.

We add one attribute to the EventView class. We need to be able to assign the controller as a delegate for our view so we can notify the controller of the flick. In order for the table to continue working, we also need to make sure we maintain the UITableViewDelegate protocol already attached to the delegate object in the super class.

Our protocol is pretty simple, we need one method implemented, eventView:flickDirection:. This will be called when the view is flicked and set the flickDirection to -1 for a left flick and 1 for a right flick.

#import "EventView.h"

#define MIN_FLICK_DISTANCE 10.0

@implementation EventView
@synthesize delegate;

- (int)flicked:(UITouch *)touch {
    CGPoint start = [touch previousLocationInView:self];
    CGPoint end = [touch locationInView:self];
   
    double x = end.x – start.x;
    double y = end.y – start.y;
    double dist = sqrt((x * x) + (y * y));
   
    if (dist > MIN_FLICK_DISTANCE) {
        if (end.x < start.x) return 1;
        else return -1;
    }
    return 0;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    int dir = [self flicked:touch];
   
    if (dir != 0) [self.delegate eventView:self flickDirection:dir];
   
    [super touchesEnded:touches withEvent:event];
}

- (void)dealloc {
    [super dealloc];
}
@end

With the header out of the way we can implement the class. The touchesEnded:withEvent: method will be executed when the touch is complete. We can access the touch event, using [touches anyObject] and look at the [touch previousLocationInView:self] and [touch locationInView:self] methods to access the two touch points.

If these points are more then MAX_FLICK_DISTANCE apart then we have a flick. As long as we have a flick we notify our delegate by calling the eventView:flickDirection: method we defined in our protocol.

With the view created, we can hook it up to our EventViewController. Open up EventViewController.h. We need to add an import for the EventView.h file and then add the EventViewDelegate protocol to the view controller.

@interface EventViewController : UITableViewController <EventViewDelegate>

Moving over to EventViewController.h we need to add an import for the RootViewController.h. We'll be accessing it's fetched results in order to get the new view information.

We need to load our new EventView as the view to use for the controller. We do this by overriding loadView

- (void)loadView
{
    EventView *eventView = [[[EventView alloc] initWithFrame:CGRectZero
                                                       style:UITableViewStyleGrouped] autorelease];
    [eventView setDelegate:self];
    [eventView setDataSource:self];

    self.view = eventView;
    self.tableView = eventView;
}

Nothing too special here, we create our EventView and tell it that the current controller is the delegate and data source for the view. Finally, we assign the new view to the controllers view and tableView methods. You need to assign both of these for things to work properly.

The last step is to implement the eventView:flickDirection: method.

- (void)eventView:(EventView *)view flickDirection:(int)dir
{
    NSFetchedResultsController *frc = [[[self.navigationController viewControllers] objectAtIndex:0] fetchedResultsController];
   
    NSIndexPath *path = [frc indexPathForObject:[self event]];
    NSInteger row = [path row];
   
    if ((dir == -1) && (row > 0)) row --;
    else if ((dir == 1) && (row < ([[frc fetchedObjects] count] - 1))) row ++;

    if (row == [path row]) return;

    NSIndexPath *newPath = [NSIndexPath indexPathForRow:row inSection:[path section]];
    [self setEvent:[frc objectAtIndexPath:newPath]];

    [self.tableView reloadData];    
}

The idea here is simple, grab the fetched results from the root view using [[[self.navigationController viewControllers] objectAtIndex:0] fetchedResultsController]. The root view is always at the bottom of the view stack. The fetched results controller can then be used to find the index for the event that we're currently viewing. We can then get the next view, either left or right based on flick direction, to be displayed. We set that event into the view and reload the table data.

Building and running you should be able to flick between views. Although, there is an issue, the visuals really suck. Let's do something about that.

In order to make things look good we should push the new views into place and, if we're on the end view, bounce a little to show the user they can't go any further.

For this, we're going to dip into a little but of Core Animation. To that end, we need to import <QuartzCore/QuartzCore.h> into the EventViewController.h file and add the QuartzCore.framework into our project.

We'll start with a little helper method.

- (void)addAnimation:(NSString *)name subtype:(NSString *)subtype duration:(float)duration
               start:(float)start end:(float)end delegate:(id)animDelegate {
    CATransition *anim = [CATransition animation];
   
    [anim setDuration:duration];
    [anim setType:kCATransitionPush];
    [anim setSubtype:subtype];
    [anim setStartProgress:start];
    [anim setEndProgress:end];
    [anim setDelegate:animDelegate];
    [anim setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
   
    [[[self.view superview] layer] addAnimation:anim forKey:name];
}

addAnimation:subtype:duration:start:end:delegate: adds a new animation to the system. We're always using push animations and always using the same ease timing function. Otherwise, we can set things up as we need.

To start, add the following to the end of eventView:flickDirection:

   [self addAnimation:@"SwitchView"
               subtype:(dir == -1 ? kCATransitionFromLeft : kCATransitionFromRight)
              duration:0.5
                 start:0.0
                   end:1.0
              delegate:nil];

If you run this, you should be able to flick between views with a nice push animation. You can change the duration to make the transition slower or faster as desired.

Now, let's add the bounce when we're at the end of our data. Inside eventView:flickDirection: change if (row == [path row]) return; to the following.

   if (row == [path row]) {
        [self addAnimation:@"PushHalfOut"
                   subtype:(dir == -1 ? kCATransitionFromLeft : kCATransitionFromRight)
                  duration:0.25
                     start:0.0
                       end:0.25
                  delegate:self];
        return;
    }

You'll notice we set the delegate in this instance to ourselves. This will cause animationDidStop:finished: to be called when the animation is finished. We're going to add that callback next.

- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag {
    [self addAnimation:@"PushHalfIn"
               subtype:[(CATransition *)animation subtype]
              duration:0.25
                 start:0.25
                   end:0.0
              delegate:nil];
}

With the first animation we're shifting the view off screen by a quarter of the screen. Then, when that animation is finished, we trigger a second animation to shift the view back on screen again. Giving the appearance of a slight bounce.

With that, we have our views flicking and bouncing. You can grab the full source to this tutorial at http://github.com/dj2/Flick.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Next Page »