Psellos
Life So Short, the Craft So Long to Learn

Gamut: Explore Colors in iOS Simulator

May 13, 2012

Posted by Jeffrey

Note: This is an archived version of the Gamut page, for those interested in earlier versions. The most recent version is at Gamut: Explore Colors in iOS Simulator.

I’ve put together a simple OCaml app that you can run in 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—it’s very 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.

The most recent version of Gamut is 1.0.17. I built and tested it under OS X 10.7 (Lion) using Xcode 4.3.2. I would expect it to work with minimal changes using future Xcode releases also. A previous version of Gamut, for earlier versions of OS X and Xcode, can be found in the OCaml Programming Archives.

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.

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 in Compile OCaml for iOS Simulator. 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. This makes it easy to get it working.

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 more realistic apps (like our Cassino and Master Schnapsen/66 apps), 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, you need an installation of Apple’s Xcode programming tools, which contain the iOS Simulator as one part. As I write this, the current version is Xcode 4.3.2.

You can download Xcode (for free) from the Mac App Store. See Apple’s Xcode page for more details. After installing Xcode, you need to go to the Downloads -> Components page of its Preferences and also download the “Command Line Tools” and the “iOS 5.0 Simulator.”

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 in Compile OCaml for iOS Simulator.

Running Prebuilt Gamut Binary

To run the prebuilt Gamut binary, just download the binary for Gamut Launcher 1.0.17 from Psellos:

Now look in your Downloads folder (where your browser places downloaded files). You should see a file named gamut-simapp-1.0.17.zip. Your browser may have opened it for you automatically. If not, double click on it. You’ll see an app named GamutLauncher, which looks like this in the finder:

GamutLauncher icon

Just double click on GamutLauncher to launch the iOS Simulator with Gamut installed in it. Gamut will be on the second screenful of apps—swipe to the left to see it. Start Gamut by clicking on its icon.

If the iOS Simulator doesn’t start up, make sure you’ve installed Xcode (version 4.2 or later) and the iOS Simulator as described above. Another reason for failure is if you have Xcode installed in a non-standard place (not in the /Applications folder). Drag and drop your Xcode application onto GamutLauncher—this asks GamutLauncher to make a note of the location of your Xcode. Then try starting up GamutLauncher again.

If the iOS Simulator starts but Gamut doesn’t appear, you may need to change the version number of the simulated iOS. The launcher installs Gamut inside the iOS 5.1 Simulator. To change the version number, select 5.1 from the Hardware -> Version menu of the simulator.

Internally, GamutLauncher runs a script to install Gamut in the iOS Simulator and then start up the simulator. I wrote about this script, named runsim, in a recent blog post Run iOS Simulator from the Command Line (Improved).

All the required files are inside GamutLauncher in a folder named Resources. If you want to try it yourself, here are the commands to run the script:

$ cd GamutLauncher.app/Contents/Resources
$ runsim Gamut

If your Xcode is installed in a nonstandard place, edit the file runsim.xcloc. It should contain the full path of your Xcode app. (You can also set it by dragging and dropping Xcode onto GamutLauncher, as described above.)

Note: for simplicity, the runsim script uses an unsupported interface. It may need to be updated for future versions of the simulator.

Building Gamut from Source

Download the sources for Gamut 1.0.17 from Psellos:

Now look in your Downloads folder (where your browser places downloaded files). You should see a file named gamut-sim-1.0.17.zip. Your browser may have opened it for you automatically. If not, double click on it. This creates the project folder, named gamut-sim-1.0.17. You may want to move it out of the Downloads folder to a more convenient place.

Build and Run from Xcode

The project folder contains an Xcode project description that you can use to build and run Gamut. Double click on GamutSim.xcodeproj to start up Xcode. You can also open it from Xcode’s File -> Open menu.

Make sure Xcode is building the Gamut target (not Gamutbin) and is building for the iOS simulator (not a device).

  • Click at the left side of the Scheme selector at the left end of the toolbar. A menu drops down. Select Gamut -> iPhone 5.1 Simulator.

The file Makefile.iossim contains the instructions for building Gamut. You may need to change some of the specific settings in this file. Make sure you are in the Project Navigator, which shows the files of your project. If necessary, click the triangle next to GamutSim to reveal them. Click Makefile.iossim in the left pane. You’ll see lines like this at the top:

PLAT = /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform
SDK = /Developer/SDKs/iPhoneSimulator5.1.sdk
OCAMLDIR = /usr/local/ocamlxsim

Change PLAT to the location of your iPhoneSimulator platform folder, 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.

You can now build and run the app.

  • To build and run, click on the Product -> Run menu item.

If things go right, you’ll see the Gamut app running in the simulator.

You may see the following warning in the Xcode debugger console:

2012-05-13 17:04:37.045 Gamut[10298:f803] Application windows are expected to have a root view controller at the end of application launch

This warning can be ignored. Gamut is targeted at iOS 3.1 and later, while the rootViewController property of windows wasn’t added until iOS 4.0. Unfortunately, this warning is issued even when (as in this case) it doesn’t necessarily make sense.

It would be an interesting project to retarget Gamut for iOS 4.0 and to add a root view controller of the main window. In fact I’ve tried this experiment, and it works well. But I decided to stick with the iOS 3.1 target for now.

Build and Run from Command Line

To build and run from the command line, unpack the project folder and edit Makefile.iossim as above. Use Terminal (or equivalent) to cd into the folder. Then use make and runsim.

$ make -f Makefile.iossim
   ...
ibtool --compile Gamut.nib Gamut.xib
$ runsim Gamut

The iOS Simulator should start up, and Gamut will be on the second page of apps, as described above.

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 nibfile, 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

You can browse the structure of the nibfile in Xcode. The source for the nibfile is named Gamut.xib. To examine it, open Xcode as above. Activate the Project Navigator by clicking the Navigate -> Reveal in Project Navigator menu item. You’ll see Gamut.xib in the list of files at the left. Click on Gamut.xib to select it.

Interface Builder will display the nibfile in the center and to the right. There is a list of user interface objects, and a schematic drawing in the middle that shows what they will look like. You can see that the nibfile is pretty simple. There are only three interesting objects: the main window, a ViewDelegator instance that occupies the whole window, and Gcon.

Interactions among objects are controlled by connections. 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 doesn’t know anything about OCaml (at least not yet), it gets its information from the header files for the wrapper classes. Note that you need wrappers only for OCaml classes that are accessed from ObjC, which should be just a few of them. In Gamut, the header files are ViewDelegator.h and wrap.h.

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 nibfile 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 Compile OCaml for iOS. I’ve also put together some example 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. For a full list of OCaml-on-iOS programming resources, see our OCaml Programming page.

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 leave a comment below or email me at jeffsco@psellos.com.