46

I'm making a drawing app and I would like to refer to my colors through use of an enum. For example, it would be cleaner and more convenient to use Colors.RedColor instead of typing out values every time I want that red color. However, Swift's raw value enums don't seem to accept UIColor as a type. Is there a way to do this with an enum or something similar?

A Tyshka
  • 3,830
  • 7
  • 24
  • 46
  • 1
    "However, Swift's raw value enums don't seem to accept UIColor as a type" Correct. A raw value enum can only have a string or number literal as a value. – matt Aug 24 '16 at 01:23

9 Answers9

74

I do it like this (basically using a struct as a namespace):

extension UIColor {
  struct MyTheme {
    static var firstColor: UIColor  { return UIColor(red: 1, green: 0, blue: 0, alpha: 1) }
    static var secondColor: UIColor { return UIColor(red: 0, green: 1, blue: 0, alpha: 1) }
  }
}

And you use it like:

UIColor.MyTheme.firstColor

So you can have a red color inside your custom theme.

Elijah
  • 8,381
  • 2
  • 55
  • 49
FranMowinckel
  • 4,233
  • 1
  • 30
  • 26
  • 1
    For me this is by far the best solution! – ixany Sep 11 '17 at 08:38
  • If it autocompletes like an enum then its the best solution – amar Oct 23 '18 at 07:29
  • Instead of doing `static var prop: Type { return Type.init() }`, could you do `static var prop = Type.init()` ? – agirault Oct 31 '19 at 19:17
  • initialize colors as this static var customGray: UIColor { return UIColor(red: 227.0 / 255.0, green: 227.0 / 255.0, blue: 227.0 / 255.0, alpha: 1.0) } – Fido Feb 26 '20 at 07:56
  • 1
    This is great. To add onto this, I like to use an enum as a namespace since enums can't be initialized whereas structs can. This solution would allow MyTheme to be initialized which is not the point of this struct as its acting as a namespace and could lead to unexpected behaviors. – apunn Jul 17 '20 at 21:33
  • @Fido why the `: UIColor { return ... }` instead of just `= ...` ? – agirault Dec 03 '20 at 23:33
38

If your color isn't one of those defined by UIColor's convenience method, you can add an extension to UIColor:

extension UIColor {
    static var firstColor: UIColor  { return UIColor(red: 1, green: 0, blue: 0, alpha: 1) }
    static var secondColor: UIColor { return UIColor(red: 0, green: 1, blue: 0, alpha: 1) }
}

// Usage
let myColor = UIColor.firstColor
Elijah
  • 8,381
  • 2
  • 55
  • 49
Code Different
  • 90,614
  • 16
  • 144
  • 163
  • And in fact in Swift 3 the built-in colors are class variables too, so that fits in nicely. – matt Aug 24 '16 at 01:24
  • Here is the problem: I couldn't do `static var red: UIColor` because UIColor already has a `red` property (In Swift 3). I could call it something else like `redColor` but then that seems like bad practice to have both `red` and `redColor` properties – A Tyshka Aug 25 '16 at 00:58
  • 2
    Then don't call it `red`, call it something else like `myRed` if your color is not `#FF0000` – Code Different Aug 25 '16 at 01:00
27

I use computed properties to solve this problem, this is my code

enum MyColor {
    case navigationBarBackgroundColor
    case navigationTintCololr
}

extension MyColor {
    var value: UIColor {
        get {
            switch self {
            case .navigationBarBackgroundColor:
                return UIColor(red: 67/255, green: 173/255, blue: 247/255, alpha: 1.0)
            case .navigationTintCololr:
                return UIColor.white
            }
        }
    }
}

then I can use MyColor like this:

MyColor.navigationBarBackgroundColor.value
Teng
  • 369
  • 3
  • 8
  • 1
    This would create a new `UIColor` value on every access, which can cause some overhead. – Koen. Oct 24 '19 at 09:29
6

How can I make a Swift enum with UIColor value?

This is how you would literally make an enum with a UIColor value:

import UIKit

final class Color: UIColor, RawRepresentable, ExpressibleByStringLiteral
{
    // MARK:- ExpressibleByStringLiteral

    typealias StringLiteralType = String

    convenience init(stringLiteral: String) {
        guard let (a,r,g,b) = Color.argb(hexColor: stringLiteral) else {
            assertionFailure("Invalid string")
            self.init(red: 0, green: 0, blue: 0, alpha: 0)
            return
        }
        self.init(red: r, green: g, blue: b, alpha: a)
    }

    // MARK:- RawRepresentable

    public typealias RawValue = String

    convenience init?(rawValue: RawValue) {
        guard let (a,r,g,b) = Color.argb(hexColor: rawValue) else { return nil }
        self.init(red: r, green: g, blue: b, alpha: a)
    }

    var rawValue: RawValue {
        return hexString()
    }

    // MARK:- Private

    /// Return color components in range [0,1] for hexadecimal color strings.
    /// - hexColor: case-insensitive string with format RGB, RRGGBB, or AARRGGBB.
    private static func argb(hexColor: String) -> (CGFloat,CGFloat,CGFloat,CGFloat)?
    {
        let hexAlphabet = "0123456789abcdefABCDEF"
        let hex = hexColor.trimmingCharacters(in: CharacterSet(charactersIn: hexAlphabet).inverted)
        var int = UInt32()
        Scanner(string: hex).scanHexInt32(&int)
        let a, r, g, b: UInt32
        switch hex.count {
        case 3: (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) // RGB
        case 6: (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) // RRGGBB
        case 8: (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) // AARRGGBB
        default: return nil
        }
        return (CGFloat(a)/255, CGFloat(r)/255, CGFloat(g)/255, CGFloat(b)/255)
    }

    private func hexString() -> String {
        var red:   CGFloat = 0
        var green: CGFloat = 0
        var blue:  CGFloat = 0
        var alpha: CGFloat = 0
        if self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) {
            return String(format: "#%02X%02X%02X%02X", UInt8(red * 255), UInt8(green * 255), UInt8(blue * 255), UInt8(alpha * 255))
        }
        assertionFailure("Invalid colour space.")
        return "#F00"
    }
}

enum Colors: Color {
    case red = "#F00"
//    case blue = "#F00" // Raw value for enum case is not unique
}

let color3 = Color(rawValue: "#000") // RGB
let color6 = Color(rawValue: "#123456") // RRGGBB
let color8 = Color(rawValue: "#12345678") // AARRGGBB
print(Colors(rawValue:"#F00") as Any) // red
print(Colors(rawValue:"#FF0000") as Any) // red
print(Colors(rawValue:"#FFFF0000") as Any) // red
print(Colors(rawValue:"#ABC") as Any) // nil because it’s not a member of the enumeration
// print(Colors(rawValue:"#XYZ") as Any) // assertion on debug, black on release
print(Colors.red) // red
print(Colors.red.rawValue) // UIExtendedSRGBColorSpace 1 0 0 1

With help from

Jano
  • 62,815
  • 21
  • 164
  • 192
3

This can be done much more succinctly (and should):

extension UIColor
{
    static let myColor = UIColor(displayP3Red: 0.0, green: 0.7, blue: 0.0, alpha: 1.0)
}

(Any other method that returns a UIColor is equally suitable, doesn't need to be displayP3Red)

Usage:

let someColor: UIColor = .myColor
HenryRootTwo
  • 2,572
  • 1
  • 27
  • 27
  • so much better than having to type out the "MyTheme" every time. – zumzum Jun 03 '20 at 19:09
  • Great solution. I don't understand why people use computed properties when it's far from necessary. We basically do this, but used named colors in the Assets. – Olav Gausaker Feb 01 '21 at 15:16
2

Actually I use such implementation, it is very convenience for me because of two reason, first one I can use dex value and another all colors in constant

import UIKit

struct ColorPalette {
struct Gray {
    static let Light = UIColor(netHex: 0x595959)
    static let Medium = UIColor(netHex: 0x262626)
}
}

extension UIColor {
convenience init(red: Int, green: Int, blue: Int) {
    assert(red >= 0 && red <= 255, "Invalid red component")
    assert(green >= 0 && green <= 255, "Invalid green component")
    assert(blue >= 0 && blue <= 255, "Invalid blue component")

    self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0)
}

convenience init(netHex: Int) {
    self.init(red: (netHex >> 16) & 0xff, green: (netHex >> 8) & 0xff, blue: netHex & 0xff)
}
}

usage

let backgroundGreyColor = ColorPalette.Gray.Medium.cgColor
Sirop4ik
  • 4,543
  • 2
  • 54
  • 121
2

If you want to return multiple value then use below code...it's absolutely working for me....

enum GetDriverStatus : String {
    case ClockIn            = "Clock In"
    case TripStart          = "Trip Start"
    case BeaconTouchPlant   = "Beacon Touch Plant"
    case PickUp             = "Pick Up"
    case BeaconTouchSite    = "Beacon Touch Site"
    case BeaconLeftSite     = "Beacon Left Site"
    case DropOff            = "Drop Off"
    case BreakIn            = "Break In"
    case BreakOut           = "Break Out"
    case TripEnd            = "Trip End"
    case DayEnd             = "Day End"
    //case ClockOut           = "Clock Out"

    //Get data from ID
    static var allValues: [GetDriverStatus] {
        return [
            .ClockIn,
            .TripStart,
            .BeaconTouchPlant,
            .PickUp,
            .BeaconTouchSite,
            .BeaconLeftSite,
            .DropOff,
            .BreakIn,
            .BreakOut,
            .TripEnd,
            .DayEnd
        ]
    }

    //Get Color
    var colorAndStatus: (UIColor,String) {
        get {
            switch self {
            case .ClockIn,.TripStart: //Idle
                return (UIColor(red: 248/255, green: 39/255, blue: 71/255, alpha: 1.0),"Idle") //dark pink-red
            case .BeaconTouchPlant,.PickUp:
                return (UIColor(red: 46/255, green: 180/255, blue: 42/255, alpha: 1.0),"Picking up") //Green
            case .BeaconTouchSite:
                return (UIColor(red: 252/255, green: 172/255, blue: 0/255, alpha: 1.0),"On site") //orange
            case .DropOff,.BeaconLeftSite:
                return (UIColor(red: 12/255, green: 90/255, blue: 255/255, alpha: 1.0),"Dropping off") //blue
            case .BreakIn,.BreakOut:
                return (UIColor(red: 151/255, green: 151/255, blue: 151/255, alpha: 1.0),"On break") //warm-grey-two
            case .TripEnd:
                return (UIColor.black,"Trip end")
            case .DayEnd:
                return (UIColor.black,"Done for the day")
            }
        }
    }
} 

How to use this code Passing .allvalues["index of your option"] you getting UIColor at 0 position as well as String value as 1 position

GetDriverStatus.allValues[1].colorAndStatus.0 //UIColor.Black
GetDriverStatus.allValues[2].colorAndStatus.1 //"Picking up"
kuldip bhalodiya
  • 992
  • 7
  • 11
2

Based on @Jano's answer I made an improvement by using Int as the literal type:

import UIKit

public final class Colors: UIColor {

}

extension Colors: ExpressibleByIntegerLiteral {
    public typealias IntegerLiteralType = Int

    public convenience init(integerLiteral value: Int) {
        let red = CGFloat((value & 0xFF0000FF) >> 24) / 0xFF
        let green = CGFloat((value & 0x00FF00FF) >> 16) / 0xFF
        let blue = CGFloat((value & 0x0000FFFF) >> 8) / 0xFF
        let alpha = CGFloat(value & 0x00FF00FF) / 0xFF

        self.init(red: red, green: green, blue: blue, alpha: alpha)
    }
}

extension Colors: RawRepresentable {
    public typealias RawValue = Int

    public var rawValue: RawValue {
        return hex
    }

    public convenience init?(rawValue: RawValue) {
        self.init(integerLiteral: rawValue)
    }
}

fileprivate extension UIColor {
    var hex: Int {
        var fRed: CGFloat = 0
        var fGreen: CGFloat = 0
        var fBlue: CGFloat = 0
        var fAlpha: CGFloat = 0
        if self.getRed(&fRed, green: &fGreen, blue: &fBlue, alpha: &fAlpha) {
            let red = Int(fRed * 255.0)
            let green = Int(fGreen * 255.0)
            let blue = Int(fBlue * 255.0)
            let alpha = Int(fAlpha * 255.0)
            let rgb = (alpha << 24) + (red << 16) + (green << 8) + blue
            return rgb
        } else {
            return 0x000000
        }
    }
}

public enum MainPalette: Colors {
    case red = 0xFF0000ff
    case white = 0xFFFFFFFF
}

public enum FeatureXPalette: Colors {
    case blue = 0x024F9Eff
//    case bluish = 0x024F9Eff // <- Can't do
    case red = 0xFF0000ff
}

The advantage is that it doesn't allow duplicate colors (as a true enum) and also I support alpha.

As you can see, you can create multiple enums for different palettes/schemes. In the case you want views to be able to use any palette, you can just add a protocol:

protocol Color {
    var color: UIColor { get }
}

extension MainPalette: Color {
    var color: UIColor {
        return rawValue
    }
}

extension FeatureXPalette: Color {
    var color: UIColor {
        return rawValue
    }
}

so that way you can have a function that takes in the protocol:

func printColorEquality(color1: Color, color2: Color) {
    print(color1.color == color2.color)
}

let red1: Color = MainPalette.red
let red2: Color = FeatureXPalette.red

printColorEquality(color1: red1, color2: red2)

What I also like to do is add static vars for convenience:

extension MainPalette {
    public static var brightRed: UIColor {
        return MainPalette.red.color
    }
}

that gives you a cleaner api:

view.backgroundColor = MainPalette.brightRed

Naming can be improved: you have to choose if you want a nice convenience api or nice naming for your enums.

Eric
  • 3,301
  • 4
  • 33
  • 39
2

This answer is probably late, but for others finding this question. I was not satisfied with the answers above, since adding colors as UIColors extension is not always what you want, since:

  1. It might not be the best solution from Software architecture perspective.
  2. You can not use the power enums have, e.g. CaseIterable

This is the solution I came up with:

enum PencilColor {
    case lightRed
    case darkPurple
    
    var associatedColor: UIColor {
        switch self {
        case .lightRed: return UIColor(red: 67/255, green: 173/255, blue: 247/255, alpha: 1.0)
        case .darkPurple: return UIColor(red: 67/255, green: 173/255, blue: 247/255, alpha: 1.0)
        }
    }
}
Ali Pacman
  • 719
  • 7
  • 12