iOS Design System - Colours

July 05, 2021

Creating a design system in iOS - Colours

Having a design system in your app is very useful for standardizing UI elements in your app. One core component of a design system is colour!

Today, we’ll look at a quick way to build extra SwiftUI View functions to simplify adding colours to your UI components.

Asset Catalog

First, we need to define some colours in your asset catalog. For the purposes of this article, we are using randomly generated colours. asset_catalog

Standardized Colour Updates

Would it not be convenient if we could just do something like:

Button(...)
  .background(.customBackground)

instead of:

Button(...)
  .background(Color(uiColor: UIColor(named: "green-400")!))

To get to this point, we’ll have to create a custom enum and then make a special function within the SwiftUI View protocol to get our desired effect.

Custom Color Enum

An enum will be a starting point to stop manually creating a UIColor then converting it to a Color object. First, let’s make sure we have all the colours listed inside the asset catalog in the enum.

// DSColor.swift

enum DSColor: String {
  
  // MARK: Base Colors
  case blue400 = "blue-400"
  case gray100 = "gray-100"
  case gray200 = "gray-200"
  case gray300 = "gray-300"
  case gray700 = "gray-700"
  case gray800 = "gray-800"
  case gray900 = "gray-900"
  case green400 = "green-400"
  case green800 = "green-800"
  case orange100 = "orange-100"
  case red200 = "red-200"
  case red500 = "red-500"

}

Next, we’ll include all the semantic names we need:

// DSColor.swift

enum DSColor: String {

  // MARK: Base Colors
  ...

  // MARK: Theme Colors
  case background
  case primary
  case primaryDark
  case secondary
  case secondaryDark

  // MARK: Font Colors
  /// Accent Font Color
  case fontAccent
  /// Basic Font Color
  case fontBody
  /// Disabled Font Color
  case fontDisabled

  // MARK: Semantic Colors
  case danger
  case info
  case success
  case warning

}

Note that this is an enum of String values. We need to create a property to receive Color objects based on these enum values.

// DSColor.swift

extension DSColor {

  /// Default to black if the color does not exist in the design system.
  var color: Color {
    switch self {
    case .background:
      return .white
    case .primary:
      return DSColor.green400.color
    case .primaryDark:
      return DSColor.green800.color
    case .secondary:
      return DSColor.red200.color
    case .secondaryDark:
      return DSColor.red500.color
    case .fontAccent:
      return DSColor.secondaryDark.color
    case .fontBody:
      return DSColor.gray900.color
    case .fontDisabled:
      return DSColor.gray200.color
    case .danger:
      return DSColor.secondary.color
    case .info:
      return DSColor.blue400.color
    case .success:
      return DSColor.primary.color
    case .warning:
      return DSColor.orange100.color
    default:
      guard let uiColor = UIColor(named: rawValue) else {
        return .black
      }
      return Color(uiColor)
    }
  }

}

You’ll notice that we’re pointing the semantics to the base colours defined in our asset catalog, which will end up going to the default case in the switch.

View Extension

Now that we have the enum defined, let’s make use of it within a function. An extension for the View protocol needs to be made.

// DSColor.swift

extension View {

  /**
   Modifies a view's foreground color.
   
   - parameter dsColor: Themed color to change.
   */
  public func dsColor(_ dsColor: DSColor) -> some View {
    foregroundColor(dsColor.color)
  }

  /**
   Modifies a view's background color.
   
   - parameter dsColor: Themed color to change.
   */
  public func dsBackgroundColor(_ dsColor: DSColor) -> some View {
    background(dsColor.color)
  }

}

All this does is take the current View and apply either foregroundColor() or background() and return the updated View.

That’s all there is to building a standardized colour system for your design system!

Use Previews

Let’s test how we would use it with Xcode’s fancy previews.

// MARK: - SwiftUI Preview
struct DSColor_Previews: PreviewProvider {
  
  private static let previewColors: [DSColor] = [
    .primary,
    .primaryDark,
    .secondary,
    .secondaryDark,
    .fontAccent,
    .fontBody,
    .fontDisabled,
    .danger,
    .info,
    .success,
    .warning
  ]

  static var previews: some View {
    List {
      ForEach(previewColors, id: \.rawValue) { dsColor in
        colorRect(dsColor)
          .listRowSeparator(.hidden)
      }
    }
    .listStyle(.plain)
    .dsBackgroundColor(.background)
  }

  private static func colorRect(_ dsColor: DSColor) -> some View {
    return HStack {
      Rectangle()
        .dsColor(dsColor)
        .frame(width: 50)
      Text(dsColor.rawValue)
        .dsColor(.fontBody)
    }
    .frame(height: 50)
  }

}

And here’s the result: preview

Conclusion

That’s a wrap for building the first building blocks of a design system. In the future, we can discuss further on building the typography, small UI components, etc…

For more information on this code, visit GitHub to see this implementation in detail.

Let's work together

Find out how we can help you grow.