Lummox Labs

Mobile app maker since 2015

Paletteable.

Note: For the remainder of the post we will ignore the fact that throughout the codebase the spelling "palate" was used. Shameful.

Note: Download Noodles here!


Once we settled on an abstract style for Noodles, the colour became really important. Jibran (designer extraordinaire) and I wanted a different colour for each "puzzle pack", or puzzle size. The idea was that when you pick a pack, everything gets tinted to match the colours of that pack. Then the main screen, anything before a pack is loaded, would take the colour of the last pack played. 

Here are a few simple things we did to make this as easy as possible.

Noodles really only uses two colours for each palette - a base colour for the background, and a main colour. Though everything here would work just as well with more complex palettes.

First up, Jibran needed to be able to play with the palettes really easily, to tweak colours. We created an xib file with a bunch of palettes he could play with. In code I just grab the background colour and text colour, which creates each palette. You could easily beef up the xib to include other colours, text strings, or anything else. 

I do what I can

I do what I can

Okay, now we have an easy way to choose and edit palettes, how does the code actually use them? 

First, there's a Palette object, which is a simple container for the different colours/properties.

class Palette: NSObject {
  var baseColor: UIColor = UIColor.whiteColor()
  var textColor: UIColor = UIColor.blackColor()
}

Any keen-eyed observers wondering why the Palette is an NSObject, it's only because it's used in some protocols that require it.

All view controllers have a Palette variable, and an init method that requires a Palette, and a styleWithPalate() method that sets all colours.

  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    ...
    
    self.styleWithPalate()
  }
  
  func styleWithPalate() {
    StyleUtilities.styleNavigationBar(color: self.palate.textColor)

    self.view.backgroundColor = self.palate.baseColor
    
    let labels = [self.playButton, self.nextButton]
    labels.doEach{ $0.textColor = self.palate.textColor }
    
    let buttons: [UIButton] = [self.legalButton, self.resetButton]
    buttons.doEach{ $0.setTitleColor(self.palate.textColor, forState: .Normal) }
  }

Keen-eyed observers, yes! I don't use the tintColor property at all! Mostly because it has limited use: the buttons will use it, but nothing else: borders, labels, images. However, I think you could write some clever code to style labels and borders based on their tint colours, and perhaps cascade the tint a little better. Try it out!  

You could get clever to avoid writing the (fairly repetitive) styleWithPalate method, but the app is small enough that I left it simple.

Mostly, the Palettes are passed around, so the PackViewController passes its Palette to the GameViewController, which passes to the PuzzleCompleteViewController, etc. This way there isn't a lot of singleton access of the manager that loads the colours from the xib. And Palettes aren't strongly tied to packs, which easily allows us to use them in screens (like the about screen) that have nothing to do with packs. 

What else do we do with palettes? One of the biggest things is the noodles themselves: we only have one set of assets for the pieces, and in code we colour them to create the correct UIImages.  

  class func imageFromImage(image: UIImage, cacheKey: String, withColor color: UIColor, overlayImageOpt: UIImage? = nil) -> UIImage {

    if let cachedImage = styleUtilitiesImageCache[cacheKey] {
      return cachedImage
    }
    
    let rect = CGRectMake(0.0, 0.0, image.size.width, image.size.height)
    
    UIGraphicsBeginImageContextWithOptions(rect.size, false, image.scale)
    
    let context = UIGraphicsGetCurrentContext()
    
    image.drawInRect(rect)
    
    CGContextSetFillColorWithColor(context, color.CGColor)
    CGContextSetBlendMode(context, kCGBlendModeSourceAtop)
    CGContextFillRect(context, rect)
    
    if let overlayImage = overlayImageOpt {
      overlayImage.drawInRect(rect)
    }
    
    let result: UIImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    
    styleUtilitiesImageCache[cacheKey] = result
    
    return result
  }

Note the use of a cache in there: One of the downsides to this code is that you lose the built-in caching of UIImage(named: ), so we cache the coloured images ourselves.

This code replaces the colour of the source image with the new colour, respecting the alpha channel. So semi-transparent parts of the source image still are semi-transparent in the final image. 

Our use of palettes is super simple, but I like the patterns because they scale nicely to have easily-swappable, complex palettes in the future. Enjoy!