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
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.
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
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.
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:
Running Shark with the Swift toolchain
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
!
Drag and add this file to your project, we are almost there!
Let’s take a look at the generated file:
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.
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.