Home     About     Archive

Lazy Thoughts

Shark

Note: This is post discusses Shark, which uses Swift 2.0 and it’s toolchain. Make sure you’re using the correct toolchain by doing sudo xcode-select -switch /Applications/Xcode-beta.app/Contents/Developer/ before you begin.

I’ve been working on a small Swift script called Shark for a while. It aims to leverage Swift’s strong type system in order to make image loading type-safe.

Let’s build an example project that shows how to use Shark. Then at the end, we’ll shorty discuss how it works.

Before we begin let’s see how image loading looks with and without Shark:

//Without Shark
let myImage = UIImage(named:twitter_share_checked)

//With Shark
let myImage = Shark.SocialIcons.twitter_share_checked.image

###Installing Shark Clone the repo if you haven’t already. https://github.com/kaandedeoglu/Shark. The repo contains the source to the script, and also a compiled executable. There are two ways to setup Shark:

  • Copy the Shark.swift file to your project directory. Make sure you don’t add it to your project. Keeping the file in some other location works as well, but will cause big headaches if you’re working in a team

  • Move the executable named shark to /usr/local/bin. Since the library is rapidly evolving at the time of writing, doing this every time there’s an update can be mildly annoying.

If you don’t like the sound of adding an unknown executable to your bin folder. You can take a look at the source, and use xcrun -sdk macosx swiftc Shark.swift -o shark to build your own executable.

I’m looking for ways to make the installation easier - any ideas are very welcome


##Tutorial

Start up Xcode 7. Start by creating an new “Single Screen Application”. For the purposes of this tutorial, I called mine ManyImages

Empty Project Our empty, boring project

Next, we are going to add some images to the project. After all, we called it ManyImages. For the sake of easiness, I just copied the contents of the image assets folder of another project of mine. I suggest you do the same instead of looking for images to include.

Images Added much image, very pixel, wow

I found out that using Shark, it’s almost always better to use folders to group images. This way, code completion makes it much easier to find the image you’re looking for.

Use the small + icon at the bottom left of the asset catalogue screen to add new folders

Organized Images Organize much?

Looks much better. Now to the fun part:

Go to project settings, select your target, and switch to the Build Phases tab. Use the + icon at the top of the page and select New Run Script Phase. This will create a new run script. Drag the newly created Run Script tab to place it below Target Dependencies and above Compile Sources. You can also click on the word Run Script and rename it to Shark. Your screen should look like this when you’re done.

Build Phases

Now, expand the run script (now called Shark) tab. Shark takes two parameters. The path to the image assets folder, with an extension .xcassets, and an output directory where the generated file will be written.

If you’ve followed step 1 in the installation directions. Then the script will look like:

xcrun -sdk macosx swift "${PROJECT_DIR}/Shark.swift" "${PROJECT_DIR}/ManyImages/Assets.xcassets" "${PROJECT_DIR}/ManyImages/"

And if you’ve followed step 2 and moved the shark executable in /usr/local/bin, the script will look like:

shark "${PROJECT_DIR}/ManyImages/Assets.xcassets" "${PROJECT_DIR}/ManyImages/"

Both cases are displayed below:

Script With Swiftc Running Shark with the Swift toolchain

Script With Executable Running Shark with the executable

With this setup, we’ve told Xcode to run Shark before each build. Open your project folder, and you should see a file called SharkImages.swift!

Finder

Drag and add this file to your project, we are almost there!

Let’s take a look at the generated file:

SharkImages.swift So simple, but so useful

We can see that Shark has created a nested enum for each folder, and a case for each image.

The file has also some Swift 2.0 Protocol Extension magic to add a computed property named image to each generated enum, as well as an extension on UIImage itself.

With all this hard work behind us, it’s time to reap the rewards. Switch to ViewController.swift, and let’s try loading an image using Shark.

let imageView = UIImageView(image: Shark.SocialIcons.twitter_share_checked.image)
view.addSubview(imageView)
imageView.center = view.center

Notice how we get code completion suggestions from Xcode as we type.

Code Completion 1

Code Completion 2 Who doesn’t love code completion?

With our type safe enums, we’ll never make typing mistakes when loading images. Better yet, with good enough grouping and naming, we won’t have to constantly switch to look up image names.

How it works

Under the cover, what Shark does is pretty simple. It walks down the .xcassets folder, creates a model of the folder structure, and uses that to generate a text output which get’s written to a file.

I just want to point out the data type I used to represent the folder structure. I think it’s very neat, and without Swift things might have gotten a lot uglier.

enum Resource {
        case File(String)
        case Directory((String, [Resource]))
}

This concise, 4 line enum is all it took to successfully describe the folder structure.

The fact that we can pattern match on this is plain amazing, and it allows for easy construction of recursive functions.

Consider this function signature, also from the script:

func createEnumDeclarationForResources(resources: [Resource]) -> String

When we think in terms of pattern matching, we automatically split the problem into smaller chunks. The implementation of the above function looks something like this:

var resultString = ""
for resource in resources {
	switch resource {
	case let .File(imageName):
	//A single image file, append a new case, 
	//resultString += case imageName = "imageName"
	case let .Directory(folderName, subResources):
	//A new directory, append a new enum, 
	//resultString +=  enum folderName {  createEnumDeclarationForResources(subResources)  }
  }
}

The recursive call to createEnumDeclarationForResources is easy to understand thanks to the easy decomposition of .Directory values through pattern matching.

comments powered by Disqus