Gamut: Explore Colors in iOS Simulator
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:
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.