Godfather
Godfather

Reputation: 4340

Add a custom palette to IB that propagates if a color changes iOS

I want to create a custom palette in IB where you can set colors, and if a color changes in that palette it propagates through the views. I've seen this approach http://natashatherobot.com/xcode-color-palette/, but if in the future, a color of the palette changes, you have to go to every view on the project and change that color. I tried also doing an @IBInspectable but you can't have enums (so you can map an enum to a color). I know i can just define the colors by code and then having an outlet, but the problem is I a have tons of views that I need to subclass just for changing a color like this:

class TestCommonView: CommonView {
@IBOutlet weak var borderView: UIView!
    override func awakeFromNib() {
        super.awakeFromNib()
        borderView.backgroundColor = Colors.fabrica.pastel
    }
} 

Any ideas?

Upvotes: 2

Views: 1182

Answers (1)

SwiftArchitect
SwiftArchitect

Reputation: 48524

Reconsidering this statement:

I tried also doing an @IBInspectable but you can't have enums (so you can map an enum to a color).

You can bridge over an @IBInspectable enum using Int this way:

enum ColorPalette:Int {
    case clear = 0
    case teal = 1
    case plum = 2
    case foam = 3
}
var tincture:ColorPalette = .clear

In code, access self.tincture, which is the enum you are after. In Interface Builder, use tinctureAdapter which is an Int, and therefore a de-facto @IBInspectable of type enum.

// Stored IB property
@available(*, unavailable, message="Use tincture programmatically")
@IBInspectable var tinctureAdapter:Int {
    get {
        return self.tincture.rawValue
    }
    set {
        self.tincture = ColorPalette:Int(rawValue: newValue) ?? .clear
    }
}

It may be useful to place this code in a UIColor class Extension. Using the bridge approach, you could also use @Donamite plain-English strings in IB.


Swift 3 Example

@IBDesignable class ColorSwatchView: UIView {

    enum ColorPalette: String {
        case Thistle    = "thistle"
        case Plum       = "plum"
        case Olive      = "olive"
        case Iris       = "iris"

        case Clear      = "clear"
    }

    let namedColors = [
        "thistle": UIColor(red: 216/255, green: 191/255, blue: 216/255, alpha: 1),
        "plum"   : UIColor(red: 221/255, green: 160/255, blue: 221/255, alpha: 1),
        "olive"  : UIColor(red: 128/255, green: 128/255, blue: 0/255, alpha: 1),
        "iris"   : UIColor(red: 3/255, green: 180/255, blue: 200/255, alpha: 1)
    ]

    var tincture:ColorPalette = .Clear

    // Stored IB property
    @available(*, unavailable, message: "Use tincture programmatically")
    @IBInspectable var tinctureName: String? {
        willSet {
            if let color = ColorPalette(rawValue: newValue?.lowercased() ?? "") {
                tincture = color
            }
        }
    }
}

Interface Builder

Make your custom view a child of ColorSwatchView.

enter image description here

Programmatically

override func draw(_ rect: CGRect) {
    let ctx = UIGraphicsGetCurrentContext()
    ctx?.saveGState()
    let uiColor = namedColors[tincture.rawValue] ?? UIColor.clear
    ctx?.setFillColor(uiColor.cgColor)
    ctx?.fill(bounds)
    ctx?.restoreGState()
}

► Find this solution on GitHub and additional details on Swift Recipes.

Upvotes: 2

Related Questions