Psellos
Contemporary Development With Functional Programming

Gamut: Explore Colors in iOS Simulator

April 27, 2015

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.

A few years ago I put together a simple OCaml app that you can run in the iOS Simulator. Recently I revamped it to work nicely under iOS 8.2.

As suggested in these screenshots, the app 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.

The most recent version of Gamut is 2.0.3. I built and tested it under OS X 10.10.2 (Yosemite) using Xcode 6.2. Previous versions of Gamut, for earlier versions of OS X and Xcode, can be found in the OCaml Programming Archives.

Overview

The main problems of running OCaml apps 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 an app working.

The iOS Simulator really does simulate an iOS device, so the other two problems are solved exactly as for code that runs on an iOS device. Linking to Objective C 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 Objective C classes for use from OCaml. Similarly, you have inverse wrappers written in Objective C 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.

Preliminaries

Before starting, make sure you have installed Apple’s Xcode, including the iOS Simulator SDK and the iOS Simulator itself. As I write this, the current version is Xcode 6. You can download Xcode (for free) from the Mac App Store. See Apple’s Xcode page for more details.

You also need an OCaml compiler that conforms to the iOS Simulator ABI. If you want to use the one I put together, binaries and instructions for building from source are given in Compile OCaml for iOS Simulator.

Get Gamut Sources

Choose a place to work (an empty directory might be good).

$ cd <good place to work>

Download the sources for Gamut 2.0.3 from Psellos:

$ curl -O -s http://psellos.com/pub/gamut/gamut-sim-2.0.3.tgz
$ ls -l gamut-sim-2.0.3.tgz
-rw-r--r-- 1 psellos staff 32239 Apr 18 13:45 gamut-sim-2.0.3.tgz

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

Unpack and Check Particulars

$ tar -xf gamut-sim-2.0.3.tgz
$ cd gamut-sim-2.0.3
$ ls -1
Gamut
Gamut.xcodeproj

The work of building Gamut is done by Gamut/Makefile. You may need to change some of the specific settings near the beginning:

IOSREV = 8.2

HIDEOUT = /Applications/Xcode.app/Contents/Developer
TOOLDIR = $(HIDEOUT)/Toolchains/XcodeDefault.xctoolchain/usr/bin
PLT = $(HIDEOUT)/Platforms/iPhoneSimulator.platform
SDK = /Developer/SDKs/iPhoneSimulator$(IOSREV).sdk
OCAMLDIR = /usr/local/ocamlxsim

IOSREV is the revision of the iOS SDK that you wish to use. I’ve been testing with the iOS 8.2 SDK. If your Xcode is installed in a nonstandard location, change HIDEOUT to the Developer directory inside your Xcode application. OCAMLDIR is the location of your OCaml cross compiler.

Note: If you’re using iOS 8.3, change the MFLAGS line to this: MFLAGS = -fobjc-legacy-dispatch -fobjc-abi-version=2 -miphoneos-version-min=7.1 It should be one long line. I’ll explain more fully in a forthcoming revision of this page.

Build and Run from Xcode

To open Xcode, you can use the following command:

$ open Gamut.xcodeproj

This starts Xcode and opens Gamut as an Xcode project. You can also open the project from Xcode’s File -> Open menu, or double click on Gamut.xcodeproj in the finder.

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

  • Click the left side of the scheme selector at the left end of the toolbar. From the menu select Gamut.
  • Then click a little bit to the right and select iPhone 6 from the iOS Simulator section.

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.

Here are some hints for trying out the app:

  • Touch outside the circle to change 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.

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

  • The circle in the middle cycles through the different possible light and dark variants of the basic color. Touch inside the circle to set the overall color to the current level of lightness. This is the lightness component of the color.

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

  • Touch and hold inside the circle to go through a series of lightnesses.

Updates for iOS 8

There were three major parts of the iOS 8 update.

Automatic Reference Counting The Objective C wrappers are compiled with ARC enabled. To make this work, you need a way to represent the fact that an Objective C object is referenced from the OCaml world. I represented this by a global table g_objc_to_ocaml, defined in wrap.m. It holds Objective C objects that are referenced from OCaml, and as a side benefit it maps from an Objective C object (a pointer) to the OCaml object that represents it.

View Controllers Present-day iOS apps are based more on subclasses of UIViewController than on views. This is tricky because it’s not feasible (at least not yet) to create an OCaml subclass of an Objective C class. I handled this by defining a UIViewController subclass named PSViewConDelegator that delegates all its interesting methods to an object in the OCaml world. The OCaml class of the delegate is GamutViewControllerD.t, described below.

Storyboards Recent iOS apps use storyboards and automatic layout. Once you have a design that supports view controllers, these are not difficult changes for a simple app like Gamut.

Theory of Operation

Gamut is a very simple iOS app with just two classes that aren’t wrappers.

GamutAppDelegate.t defines the application delegate. This class has a single instance that participates in the UIApplicationDelegate protocol to receive notifications of state changes. In particular, the application delegate notices when the app is activated and deactivated. The application delegate is created at startup, as specified in main.m.

GamutViewControllerD.t also has a single instance that participates in a delegate protocol. In this case, the protocol is for delegating the actions of the root (and only) view controller to this instance. There is no predefined Cocoa Touch protocol for doing this; the protocol is defined as part of Gamut (in wrap.h). This view controller delegate is the heart of the app; let’s call it Gvcd for short. Gvcd is created as specified in the storyboard file Main.storyboard.

At startup, the application delegate establishes a timer that fires periodically, calling the timerTick method of Gvcd. Then Gvcd works by responding to outside events of three kinds:

  • Touches  As Gvcd 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 set the hue and saturation values when drawing content. If inside the circle, it tracks the finishing time of the touch. This is used to set the lightness values.

  • Timer ticks  When Gvcd 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, Gvcd 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 enable the Project Navigator at the left and click on Main.storyboard in the leftmost pane, you’ll activate Interface Builder to show the storyboard for Gamut. There are only three interesting objects: the view controller, the main view, and the view controller delegate Gvcd. These are instances of classes PSViewConDelegator, PSViewDelegator, and GamutViewControllerD.

Both delegators are connected to Gvcd through their delegate outlets. You can see this by enabling the Connections Inspector at the right. These connections allow Gvcd to control the behavior of Gamut as described above.

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 Objective C, which should be just a few of them. In Gamut, the header files are PSViewConDelegatorM.h, PSViewDelegatorM.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 are to the build environment. The code is written to be independent of screen size and resolution, so it ought to run reasonably on any iOS device.

Compiling OCaml to run directly on iOS devices is described in Compile OCaml for iOS. I’ve also packaged some example apps to 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.