Psellos
Contemporary Development With Functional Programming

Gamut: Explore Colors in iOS Simulator

June 12, 2011

Posted by Jeffrey

Updated: June 13, 2011

I’ve put together a simple OCaml app that you can run on the iOS Simulator. It displays an animation that changes colors as you touch different locations on the screen. Each screen location is associated with a certain color, and (if you’re patient) you can touch every spot on the screen to try out all the possible colors of the display. Hence the app is called Gamut.

If you don’t want to build it yourself, a prebuilt binary for Gamut is available from Psellos. See below for how to extract and try it out in the iOS Simulator (it’s extremely easy). Otherwise you can get the sources from Psellos, compile using Xcode tools, and run the app in the iOS Simulator. This is also pretty easy. All you need for either approach is an installation of Xcode with the iOS SDK; you don’t need an iOS device or have to be a registered Apple developer.

Here are some hints for trying out the app:

  • Touching outside the circle changes the display to a different color. Touches near the top give red, then successively lower locations give orange, yellow, green, blue, and purple. At the bottom it wraps back around to red. This is the hue component of the color.

  • Touches toward the left give less saturated colors (more grayish and world-weary looking), touches toward the right give more saturated ones (bright and cartoonish). This is the saturation component of the color.

  • The circle in the middle animates the different possible light and dark variants of the basic color. Touching inside the circle sets the overall color to the current level of lightness. This is the lightness component of the color.

  • You can touch outside the circle and then move your finger around to go through a series of colors.

  • You can touch and hold inside the circle to go through a series of lightnesses with time.

The app has been built and tested with both Xcode 3.2.5 and Xcode 4.0.2 (the latest as of this writing).

Please note that a few of the command lines of the following discussion are too long to fit on a single line of a typical browser window. In a lot of cases there is no good place to split them into smaller lines, usually because of a long filename or URL. Take care that you enter them as a single line if you’re typing them in by hand.

Overview

The main problems of running OCaml in the iOS Simulator are:

  • Compiling OCaml with iOS Simulator ABI

  • Linking to iOS Simulator libraries

  • Using Interface Builder

For the first problem, you can use the OCamlXSim compiler that I put together, described here. The Gamut example shows how to solve the other two problems.

One problem that you don’t have with the iOS Simulator is packaging and code signing, as the simulator doesn’t require apps to be signed. Because of this, it’s not necessary to run the Xcode app itself (the IDE) at all to build and run Gamut. All of the needed work can be done by the separate tools that come with Xcode. This includes starting the app in the Simulator, which can be done directly from the command line (through an unsupported interface).

The other two problems are solved exactly as for code that runs on an iOS device. This is what you should expect, otherwise it wouldn’t be much of an iOS Simulator. Linking to ObjC functions and classes is handled by having wrappers for them in OCaml. That is, you have small OCaml classes whose only purpose is to wrap up ObjC classes for use from OCaml. Similarly, you have inverse wrappers written in ObjC that allow access to OCaml classes. These inverse wrappers also provide Interface Builder with the information it needs to lay out the GUI and create an initial application state.

Keep in mind that the Gamut app doesn’t really do all that much, so the amount of OCaml code is quite a bit less than the amount of Objective C wrapper code. In a more realistic application (like our Cassino app), almost all the code is in OCaml. Our library of Objective C wrapper code is relatively small and fixed. (In fact, we use the dynamic method invocation facility of ObjC to allow us to write almost all our wrappers in OCaml.)

Preliminaries

Before starting, make sure you have installed Apple’s Xcode and iOS SDK. I still use Xcode 3.2.5, due to small bugs in Xcode 4.0.2 that prevent it from building the 32-bit OCaml compiler itself. However, I have also built and tested Gamut with Xcode 4.0.2 and it works. If you’re going to build Gamut from sources, you also need an OCaml compiler that conforms to the iOS Simulator ABI. You can use the one I put together—binaries and instructions for building from source are given here.

Running Prebuilt Gamut Binary

If you want to run the prebuilt Gamut binary (to prove to yourself that it works), just download the binary for Gamut 1.0.9 from Psellos:

    $ curl -O -s http://psellos.com/pub/gamut/gamut-simapp-1.0.9.tgz
    $ ls -l gamut-simapp-1.0.9.tgz
    -rw-r--r--  1 psellos  staff 116111 Jun 11 13:45 gamut-simapp-1.0.9.tgz

To save typing, you can download it directly from this link:

Now untar.

    $ tar -xzf gamut-simapp-1.0.9.tgz

This creates a directory named gamut-simapp-1.0.9 in which you should see the binary (Gamut) and a script named runsim that runs the app in the iOS Simulator. To run Gamut, just cd and run the script:

    $ cd gamut-simapp-1.0.9
    $ runsim

If you don’t have Xcode installed in the usual place, you should edit the script appropriately before running it.

Note: for simplicity, this script uses an unsupported interface of the iOS Simulator. Since it’s unsupported, the interface could disappear in a future release of Xcode. There will always be a way to run the app through the Xcode IDE, of course.

Building Gamut from Source

Download the sources for Gamut 1.0.9 from Psellos:

    $ curl -O -s http://psellos.com/pub/gamut/gamut-1.0.9.tgz
    $ ls -l gamut-1.0.9.tgz
    -rw-r--r--  1 psellos  staff  12944 Jun 11 13:45 gamut-1.0.9.tgz

To save typing, you can download it directly from this link:

Now, untar and cd into the Gamut directory.

    $ tar -xzf gamut-1.0.9.tgz
    $ cd gamut-1.0.9

Building and running Gamut is done by a Makefile. You may need to change some of the specific settings in this file:

    PLAT = /Developer/Platforms/iPhoneSimulator.platform
    SDK = /Developer/SDKs/iPhoneSimulator4.2.sdk
    OCAMLDIR = /usr/local/ocamlxsim

Change PLAT to the location of your iPhoneSimulator platform directory, part of the Xcode iOS SDK. Change SDK to the iOS SDK you wish to use. Probably you want to set it to the most recent SDK that you have installed. Change OCAMLDIR to the location of your OCaml compiler for the iOS Simulator.

Now you can build and run the app:

    $ make
       ...
    ibtool --compile Gamut.nib Gamut.xib
    echo -n 'APPL????' > PkgInfo
    $ make execute

You should see the Gamut app running in the iOS Simulator.

Note: for simplicity, make execute uses an unsupported interface of the iOS Simulator. Since it’s unsupported, the interface could disappear in a future release of Xcode. There will always be a way to run the app through the Xcode IDE, of course.

Theory of Operation

Gamut is a very simple iOS app that has just one class that really does anything, named Gamutctlr. There is a singleton instance of this class that does all the work. Let’s call this instance Gcon for short.

The Gamutctlr class participates in the UIApplicationDelegate protocol, and thus Gcon receives notifications when the app starts up and shuts down.

Gamut also defines an Objective C class named ViewDelegator. It’s a subclass of the Cocoa Touch UIView class, and its instances delegate their GUI methods to a specified delegate object. In particular, instances delegate touch events and the drawing method drawRect:. The Gamutctlr class also participates in the ViewDelegate protocol, and thus Gcon also receives notifications of touch and drawing events.

The startup of an iOS app is controlled by a nib file, generated by Interface Builder. For Gamut, the file is Gamut.nib. This file says to create Gcon, and to make it the delegate for the containing Gamut app and the single, screen-sized ViewDelegator view.

So then Gcon works by responding to outside events of four kinds:

  • Application status  At startup, Gcon establishes a timer that fires periodically and calls its own timerTick' method.

  • Touches  As Gcon receives notifications of user touches, it tracks whether the touch began inside or outside the circle. If outside the circle, it tracks the most recently touched point; this is used to determine the hue and saturation values when drawing content. If inside the circle, it tracks the finishing time of the touch. This is used to determine the lightness values.

  • Timer ticks  When Gcon receives a timer tick, it updates its record of the current time, and tells the containing view that it’s time to redraw the content.

  • Redraw  When graphics contents need to be redrawn, Gcon uses Cocoa Touch methods to draw the content. The hue, saturation, and lightness values are determined based on the current time and on recent touches, as described above.

Interface Builder

If you open Gamut.xib with Interface Builder, you’ll see that it’s pretty simple. There are only three interesting objects: the main window, a ViewDelegator instance that occupies the whole window, and Gcon.

There is a connection to Gcon from the delegate outlet of the application (represented by File’s Owner). This supports the application startup notification.

There is a connection from the delegate outlet of the ViewDelegator to Gcon, and a connection from the delegator outlet of Gcon back to the ViewDelegator. These support the notifications for touches and drawing.

Since Interface Builder (IB) doesn’t know anything about OCaml (at least not yet), it gets its information from the header files for your wrapper classes. In Xcode 3.2.5 you can run Interface Builder on its own, but you need to manually tell IB to load the wrapper header files. In the downloaded nib files, this has already been done for you. If you want to create your own project from scratch, you may need to do this yourself.

To load header files into IB, click on the File -> Read Class Files menu item, and navigate to your ObjC header files, i.e., to the wrapper files for your OCaml classes. Note that you need wrappers only for OCaml classes that are accessed from ObjC, which should be just a few of them.

Discussion

Although I’ve packaged up Gamut to run in the iOS Simulator, it will naturally also run directly on an iOS device (iPhone, iPod Touch, iPad) with no changes to the code. The only changes required for iPhone and iPod Touch are to the building environment. For iPad, the nib file will also need to change. The code is written to be independent of the screen resolution, so it ought to run nicely as a native iPad app, in fact.

Compiling OCaml to run directly on iOS devices is described in an accompanying note here. I’ve also put together two apps that run on iOS devices. There is a simple app named Portland that tracks the devices’s orientation (portrait or landscape), and a slightly more complicated app named Slide24 that plays (and solves) the classic 5x5 sliding tile puzzle.

I’d be very happy to explain any part of Gamut in more detail. The only thing stopping me is my natural reticence. If you have questions, comments, or corrections please email me at jeffsco@psellos.com.