Stephen
Stephen

Reputation: 3465

Get lighter and darker color variations for a given UIColor

How to get different lighter and darker variations of a given UIColor in Swift?

enter image description here

Upvotes: 94

Views: 38972

Answers (13)

Mojtaba Hosseini
Mojtaba Hosseini

Reputation: 119302

✅ Both UIKit & AppKit: 'aka' UIColor & NSColor

With this method, you can mix colors in any platform with any color. In this case black for darker and white for lighter appearance:

// MARK: - Cross-Platform Color Alias

#if canImport(UIKit)
public typealias NativeColor = UIColor
#elseif canImport(AppKit)
public typealias NativeColor = NSColor
#endif

// MARK: - Native Multiplatform Color Mixer

public extension NativeColor {
    func mix(with target: NativeColor, amount: CGFloat) -> Self {
        var r1: CGFloat = 0, g1: CGFloat = 0, b1: CGFloat = 0, a1: CGFloat = 0
        var r2: CGFloat = 0, g2: CGFloat = 0, b2: CGFloat = 0, a2: CGFloat = 0

        #if !canImport(UIKit)
        let `self` = usingColorSpace(.sRGB)!
        let target = target.usingColorSpace(.sRGB)!
        #endif

        self.getRed(&r1, green: &g1, blue: &b1, alpha: &a1)
        target.getRed(&r2, green: &g2, blue: &b2, alpha: &a2)

        return Self(
            red: r1 * (1.0 - amount) + r2 * amount,
            green: g1 * (1.0 - amount) + g2 * amount,
            blue: b1 * (1.0 - amount) + b2 * amount,
            alpha: a1
        )
    }

    func lighter(by amount: CGFloat = 0.2) -> Self { mix(with: .white, amount: amount) }
    func darker(by amount: CGFloat = 0.2) -> Self { mix(with: .black, amount: amount) }
}

🎁 SwiftUI: Color - iOS 18

You can mix any color with black/white to get a darker/lighter version:

Color.red.mix(with: .dark, by: 0.5) // 👈 Darker Red
Color.red.mix(with: .white, by: 0.5) // 👈 Ligher Red
💡 SwiftUI: Color - iOS 14 / macOS 10.16

⚠️ Requires previous extension

extension Color {
    public func lighter(by amount: CGFloat = 0.2) -> Self { Self(NativeColor(self).lighter(by: amount)) }
    public func darker(by amount: CGFloat = 0.2) -> Self { Self(NativeColor(self).darker(by: amount)) }
}

Upvotes: 24

Fattie
Fattie

Reputation: 12621

PLS NOTE TWO CRITICAL COLOR TIPS BELOW...

Simple modern solution

extension UIColor {
    ///Take a color and return a "somewhat darker" color
    var darker: UIColor {
        var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
        guard self.getHue(&h, saturation: &s, brightness: &b, alpha: &a) else {
            print("** OS problem")
            return self
        }
        let nudged = b * 0.5
        return UIColor(hue: h, saturation: s, brightness: nudged, alpha: a)
    }
}

usage something.color = .yellow.darker

It's that easy.

As explained by @MarkA.Donohoe, if you prefer a function the correct naming in UIKit would be:

 .adjustingBrightness(byMultiplier f: CGFloat)

ie, let nudged = b * f

Save you typing:

extension UIColor {
    ///Take a color and return a "somewhat darker" color
    var darker: UIColor {
        var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
        guard self.getHue(&h, saturation: &s, brightness: &b, alpha: &a) else {
            print("** OS problem")
            return self
        }
        let nudged = b * 0.5
        return UIColor(hue: h, saturation: s, brightness: nudged, alpha: a)
    }
    
    ///Adjust color with UIKit style function naming :)
    func adjustingBrightness(byMultiplier: CGFloat) -> UIColor {
        var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
        guard self.getHue(&h, saturation: &s, brightness: &b, alpha: &a) else {
            print("** OS problem")
            return self
        }
        print(">>", h, s, b, a)
        let nudged = b + ( (1.0 - b) * byMultiplier )
        print(">> NEW B", nudged)
        return UIColor(hue: h, saturation: s, brightness: nudged, alpha: a)
    }
}

TIP 1

Note that when dealing with modern UIKit colors you almost always want something like

c = UIColor.label.resolvedColor(with: .current).adjusting ...etc

rather than

c = UIColor.label.adjusting ... etc

Google as needed.

TIP 2

Talk to your color designers, adjusting colors is not so easy conceptually. (How do you make black "brighter"? Who knows.)

Here's what you typically want in many or most cases when you want to "lighten up / brighten up" a color:

///Example. Original b is 0.2.  Hence 0.8 "remaining". Pass in a fraction of 0.5. Half of 0.8 is 0.4, so result is 0.6
func adjustingBrightness(byFractionallyIncreasingTowardsOne fito: CGFloat) -> UIColor {
    var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
    guard self.getHue(&h, saturation: &s, brightness: &b, alpha: &a) else {
        print("** OS problem")
        return self
    }
    print(">>", h, s, b, a)
    let nudged = b + ( (1.0 - b) * fito )
    print(">> NEW B", nudged)
    return UIColor(hue: h, saturation: s, brightness: nudged, alpha: a)
}

Upvotes: 15

atultw
atultw

Reputation: 1021

I'm using SwiftUI and was looking for a quick solution.

This method changes the alpha channel (0 is transparent, 1 is opaque) and puts it in front of a white color view, so you're actually mixing white with a color. Higher alpha value, more white mixed in = brighter.

Converting the Color to UIColor, modifying, and converting back does the job:

Color(UIColor(Color.blue).withAlphaComponent(0.5))
    .background(Color.white) // IMPORTANT: otherwise your view will be see-through

To darken a color change Color.white to Color.black

Upvotes: 1

Pavel Lobodinský
Pavel Lobodinský

Reputation: 1158

For macOS apps, there is an in-built function for color blending.

To make a color lighter, simply call

NSColor.systemRed.blended(withFraction: 0.35, of: .white)

Upvotes: 1

Tran Quan
Tran Quan

Reputation: 1086

I want to provide another version using brightness & saturation instead of RGB

extension UIColor {
  /**
   Create a lighter color
   */
  func lighter(by percentage: CGFloat = 30.0) -> UIColor {
    return self.adjustBrightness(by: abs(percentage))
  }
  
  /**
   Create a darker color
   */
  func darker(by percentage: CGFloat = 30.0) -> UIColor {
    return self.adjustBrightness(by: -abs(percentage))
  }
  
  /**
   Try to increase brightness or decrease saturation
   */
  func adjustBrightness(by percentage: CGFloat = 30.0) -> UIColor {
    var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
    if self.getHue(&h, saturation: &s, brightness: &b, alpha: &a) {
      if b < 1.0 {
        let newB: CGFloat = max(min(b + (percentage/100.0)*b, 1.0), 0.0)
        return UIColor(hue: h, saturation: s, brightness: newB, alpha: a)
      } else {
        let newS: CGFloat = min(max(s - (percentage/100.0)*s, 0.0), 1.0)
        return UIColor(hue: h, saturation: newS, brightness: b, alpha: a)
      }
    }
    return self
  }
}

Upvotes: 61

Warpling
Warpling

Reputation: 2095

Swift 4 version that supports RGBA, HSBA, and WB (greyscale)

Here's a variation of TranQuan's answer that also supports greyscale colors like .white and .black. (Note: I removed saturation adjustment because I didn't think it belonged in a simple function like this.)

extension UIColor {
    /**
     Create a ligher color
     */
    func lighter(by percentage: CGFloat = 10.0) -> UIColor {
        return self.adjustBrightness(by: abs(percentage))
    }

    /**
     Create a darker color
     */
    func darker(by percentage: CGFloat = 10.0) -> UIColor {
        return self.adjustBrightness(by: -abs(percentage))
    }

    /**
     Try to adjust brightness and falls back to adjusting colors if necessary
     */
    func adjustBrightness(by percentage: CGFloat) -> UIColor {
        var alpha, hue, saturation, brightness, red, green, blue, white : CGFloat
        (alpha, hue, saturation, brightness, red, green, blue, white) = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)

        let multiplier = percentage / 100.0

        if self.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) {
            let newBrightness: CGFloat = max(min(brightness + multiplier*brightness, 1.0), 0.0)
            return UIColor(hue: hue, saturation: saturation, brightness: newBrightness, alpha: alpha)
        }
        else if self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) {
            let newRed: CGFloat = min(max(red + multiplier*red, 0.0), 1.0)
            let newGreen: CGFloat = min(max(green + multiplier*green, 0.0), 1.0)
            let newBlue: CGFloat = min(max(blue + multiplier*blue, 0.0), 1.0)
            return UIColor(red: newRed, green: newGreen, blue: newBlue, alpha: alpha)
        }
        else if self.getWhite(&white, alpha: &alpha) {
            let newWhite: CGFloat = (white + multiplier*white)
            return UIColor(white: newWhite, alpha: alpha)
        }

        return self
    }
}

Upvotes: 3

Hikeland
Hikeland

Reputation: 416

Since I use SwiftUI in my current project, I adapted the best answer from Stephen. Tested with Xcode 12.0, SwiftUI 2 and iOS 14.0

extension Color {
    var components: (red: CGFloat, green: CGFloat, blue: CGFloat, opacity: CGFloat) {
        #if canImport(UIKit)
        typealias NativeColor = UIColor
        #elseif canImport(AppKit)
        typealias NativeColor = NSColor
        #endif

        var r: CGFloat = 0
        var g: CGFloat = 0
        var b: CGFloat = 0
        var o: CGFloat = 0

        guard NativeColor(self).getRed(&r, green: &g, blue: &b, alpha: &o) else {
            return (0, 0, 0, 0)
        }
        return (r, g, b, o)
    }
    
    func lighter(by percentage: CGFloat = 30.0) -> Color {
        return self.adjust(by: abs(percentage) )
    }

    func darker(by percentage: CGFloat = 30.0) -> Color {
        return self.adjust(by: -1 * abs(percentage) )
    }

    func adjust(by percentage: CGFloat = 30.0) -> Color {
        return Color(red: min(Double(self.components.red + percentage/100), 1.0),
                     green: min(Double(self.components.green + percentage/100), 1.0),
                     blue: min(Double(self.components.blue + percentage/100), 1.0),
                     opacity: Double(self.components.opacity))
    }
}

Upvotes: 15

Mr.Javed Multani
Mr.Javed Multani

Reputation: 13274

enter image description here

The code example below demonstrate how you can get a lighter and darker shade of a given color, useful in applications having dynamic themes

For Darker Color

+ (UIColor *)darkerColorForColor:(UIColor *)c
{
CGFloat r, g, b, a;
    if ([c getRed:&r green:&g blue:&b alpha:&a])
return [UIColor colorWithRed:MAX(r - 0.2, 0.0)
                               green:MAX(g - 0.2, 0.0)
                                blue:MAX(b - 0.2, 0.0)
return nil; 
}

For Lighter Color

+ (UIColor *)lighterColorForColor:(UIColor *)c
{
CGFloat r, g, b, a;
    if ([c getRed:&r green:&g blue:&b alpha:&a])
return [UIColor colorWithRed:MIN(r + 0.2, 1.0)
                       green:MIN(g + 0.2, 1.0)
                        blue:MIN(b + 0.2, 1.0)
alpha:a];
return nil;
}

Upvotes: 0

BSK-Team
BSK-Team

Reputation: 1810

For Swift 5.0 :

extension UIColor {

func lighter(by percentage: CGFloat = 10.0) -> UIColor {
    return self.adjust(by: abs(percentage))
}

func darker(by percentage: CGFloat = 10.0) -> UIColor {
    return self.adjust(by: -abs(percentage))
}

func adjust(by percentage: CGFloat) -> UIColor {
    var alpha, hue, saturation, brightness, red, green, blue, white : CGFloat
    (alpha, hue, saturation, brightness, red, green, blue, white) = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)

    let multiplier = percentage / 100.0

    if self.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) {
        let newBrightness: CGFloat = max(min(brightness + multiplier*brightness, 1.0), 0.0)
        return UIColor(hue: hue, saturation: saturation, brightness: newBrightness, alpha: alpha)
    }
    else if self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) {
        let newRed: CGFloat = min(max(red + multiplier*red, 0.0), 1.0)
        let newGreen: CGFloat = min(max(green + multiplier*green, 0.0), 1.0)
        let newBlue: CGFloat = min(max(blue + multiplier*blue, 0.0), 1.0)
        return UIColor(red: newRed, green: newGreen, blue: newBlue, alpha: alpha)
    }
    else if self.getWhite(&white, alpha: &alpha) {
        let newWhite: CGFloat = (white + multiplier*white)
        return UIColor(white: newWhite, alpha: alpha)
    }

    return self
    }
}

Upvotes: 4

Oscar
Oscar

Reputation: 1929

Using lukszar clampled function, I wrote this function for the UIColor extension, using real proportions of RGB values. I hope it is helpful

public extension UIColor {

  public func lighter(by percentage: CGFloat = 30.0) -> UIColor {
    return self.adjustBrightness(by: abs(percentage))
  }

  public func darker(by percentage: CGFloat = 30.0) -> UIColor {
    return self.adjustBrightness(by: -abs(percentage))
  }

  func adjustBrightness(by percentage: CGFloat = 30.0) -> UIColor {

    let ratio = percentage/100

    var red:   CGFloat = 0.0
    var green: CGFloat = 0.0
    var blue:  CGFloat = 0.0
    var alpha: CGFloat = 0.0

    if self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) {
      let newRed =   (red   + ((ratio < 0) ? red   * ratio : (1 - red)   * ratio)).clamped(to: 0.0 ... 1.0)
      let newGreen = (green + ((ratio < 0) ? green * ratio : (1 - green) * ratio)).clamped(to: 0.0 ... 1.0)
      let newBlue =  (blue  + ((ratio < 0) ? blue  * ratio : (1 - blue)  * ratio)).clamped(to: 0.0 ... 1.0)
      return UIColor(red: newRed, green: newGreen, blue: newBlue, alpha: alpha)
    }
    return self
  }
}

Upvotes: 0

Stephen
Stephen

Reputation: 3465

Updated

Use below UIColor Extension:

extension UIColor {

    func lighter(by percentage: CGFloat = 30.0) -> UIColor? {
        return self.adjust(by: abs(percentage) )
    }

    func darker(by percentage: CGFloat = 30.0) -> UIColor? {
        return self.adjust(by: -1 * abs(percentage) )
    }

    func adjust(by percentage: CGFloat = 30.0) -> UIColor? {
        var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0
        if self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) {
            return UIColor(red: min(red + percentage/100, 1.0),
                           green: min(green + percentage/100, 1.0),
                           blue: min(blue + percentage/100, 1.0),
                           alpha: alpha)
        } else {
            return nil
        }
    }
}

Usage:

let color = UIColor(red:0.96, green:0.54, blue:0.10, alpha:1.0)
color.lighter(30) // returns lighter color by 30%
color.darker(30) // returns darker color by 30%

instead of .lighter() and .darker(), you can use .adjust() with positive values for lightening and negative values for darkening

color.adjust(-30) // 30% darker color
color.adjust(30) // 30% lighter color

Output:

enter image description here

Upvotes: 182

lukszar
lukszar

Reputation: 1422

Version with RGB values modification

Here I put simple UIColor extension which is based on previous answers. It's working perfectly for me.

Below demo:

Colors demo

Colors manipulation code

public extension UIColor {

    /**
     Create a lighter color
     */
    public func lighter(by percentage: CGFloat = 30.0) -> UIColor {
        return self.adjustBrightness(by: abs(percentage))
    }

    /**
     Create a darker color
     */
    public func darker(by percentage: CGFloat = 30.0) -> UIColor {
        return self.adjustBrightness(by: -abs(percentage))
    }

    /**
     Changing R, G, B values
     */

    func adjustBrightness(by percentage: CGFloat = 30.0) -> UIColor {

        var red: CGFloat = 0.0
        var green: CGFloat = 0.0
        var blue: CGFloat = 0.0
        var alpha: CGFloat = 0.0

        if self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) {

            let pFactor = (100.0 + percentage) / 100.0

            let newRed = (red*pFactor).clamped(to: 0.0 ... 1.0)
            let newGreen = (green*pFactor).clamped(to: 0.0 ... 1.0)
            let newBlue = (blue*pFactor).clamped(to: 0.0 ... 1.0)

            return UIColor(red: newRed, green: newGreen, blue: newBlue, alpha: alpha)
        }

        return self
    }
}

Clamped function Extension to easily keep values between min and max.

extension Comparable {

    func clamped(to range: ClosedRange<Self>) -> Self {

        if self > range.upperBound {
            return range.upperBound
        } else if self < range.lowerBound {
            return range.lowerBound
        } else {
            return self
        }
    }
}

Upvotes: 3

ChrisDL
ChrisDL

Reputation: 39

Kenji-Tran's answer works fine, as long as your starting color is not black (brightness value 0). With the addition of a few lines of extra code, you can also make black "lighter" (i.e. brighten it to a grayscale or color value).

Note: I wasn't able to add this change using an Edit and I'm not allowed to comment on Kenji-Tran's answer due to my "new boy" rep, therefore I found no other way to share my knowledge on SO then by posting a new answer. I hope that's okay.

extension UIColor {
  /**
   Create a ligher color
   */
  func lighter(by percentage: CGFloat = 30.0) -> UIColor {
    return self.adjustBrightness(by: abs(percentage))
  }

  /**
   Create a darker color
   */
  func darker(by percentage: CGFloat = 30.0) -> UIColor {
    return self.adjustBrightness(by: -abs(percentage))
  }

  /**
   Try to increase brightness or decrease saturation
   */
  func adjustBrightness(by percentage: CGFloat = 30.0) -> UIColor {
    var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
    if self.getHue(&h, saturation: &s, brightness: &b, alpha: &a) {
      if b < 1.0 {
        /**
         Below is the new part, which makes the code work with black as well as colors
        */
        let newB: CGFloat
        if b == 0.0 {
            newB = max(min(b + percentage/100, 1.0), 0.0)
        } else {
            newB = max(min(b + (percentage/100.0)*b, 1.0), 0,0)
        }
        return UIColor(hue: h, saturation: s, brightness: newB, alpha: a)
      } else {
        let newS: CGFloat = min(max(s - (percentage/100.0)*s, 0.0), 1.0)
        return UIColor(hue: h, saturation: newS, brightness: b, alpha: a)
      }
    }
    return self
  }
}

Upvotes: 3

Related Questions