-1

The following code successfully detects if the color is black, but it doesn't detect when the color is white or any other color.

Any idea why the following code only works to detect black but not white?

func bgColor(bgColor: Color?)->Color{
    var col:Color = .systemBackground

    if bgColor == Color(UIColor(red: 0, green: 0, blue: 0, alpha: 1)){
        // Works fine, detects black
        col = .red
    } else if bgColor == Color(UIColor(red: 1, green: 1, blue: 1, alpha: 1)){
        // Does NOT work, doesn't detect white
        col = .blue
    }
    return col
}

EDIT:

I have a ViewModifier that I'm using on a symbol-avatar-image, in this avatar image the user can select a color from a Color Picker, my issue is that I have the .systemBackground as the default background color, and if the user selects white or black for their avatar, black is not visible in dark mode and white is not visible in light mode so, what I would like to do is detect if the user selected white, I need to return a dark background color for light mode and if the user selects black I would like to return a light background for when in dark mode.

struct AvatarImageModifier : ViewModifier {
    @Environment(\.colorScheme) var colorScheme
    var avatarColor:Color?
    
    func body(content: Content) -> some View { content
        .background(bgColor(bgColor: avatarColor))
    }
    
    func bgColor(bgColor: Color?)->Color{
        var col:Color = .systemBackground
        
        if bgColor == Color(UIColor(red: 0, green: 0, blue: 0, alpha: 1)){
            if colorScheme == .dark{
                col = .systemGray3
            }else{
                col = .systemBackground
            }
        } else if bgColor == Color(UIColor(red: 1, green: 1, blue: 1, alpha: 1)){
            if colorScheme == .light{
                col = .systemGray3
            }
        }
        return col
    }
}
fs_tigre
  • 10,650
  • 13
  • 73
  • 146
  • 1
    Surely you are setting the background colour in the first place? Can you not just use the colour that you are using to set it initially? Or, is this a question about dark mode/light mode? In which case you are going down the wrong path here. – Fogmeister Nov 27 '21 at 17:24
  • 3
    If you are trying to detect Color black and white why are you not comparing to Color black and white ? The issue here is that by dancing round the moon like this with your UIColor conversion you've ended up in the wrong color space. White is not an RGB color, it's a Grey color. Your code should say what you mean. – matt Nov 27 '21 at 17:25
  • 1
    What is it that you are actually trying to do here? Thanks. I feel like trying to detect if a colour is black or white is not the right solution. What is the goal of what you’re intending to do. Thanks – Fogmeister Nov 27 '21 at 17:26
  • @Fogmeister - See my updated answer, it may help to clarify the confusion a little bit. Thanks. – fs_tigre Nov 27 '21 at 17:50
  • @matt - It's funny but when comparing with `.white` or `.black` the comparison fails even for black. – fs_tigre Nov 27 '21 at 17:51
  • 1
    regarding your edit, the problem isn't only for black and white as I see it because other very dark or very light colours will cause the same problem so you would need to define what "very dark" is and compare the user selected colour with that value to determine what background to use (and the same for light colours). – Joakim Danielson Nov 27 '21 at 18:02
  • True, but I was hoping to start with black and white and see how other dark/light colors look like. Thanks. – fs_tigre Nov 27 '21 at 18:17
  • I would suggest getting HSB components of the colour and comparing the brightness component to some threshold to determine the dark/light, and maybe the saturation component also if you want to detect that it is desaturated. – Arkku Nov 27 '21 at 18:37
  • FYI what you seems to be missing here is that SwiftUI `Color` isn't a color (i.e. rgba value), it's a `View`. – Claus Jørgensen Nov 27 '21 at 18:45
  • @Arkku I'm pretty sure you can't obtain the HSB components of a `Color` view. – Claus Jørgensen Nov 27 '21 at 18:45
  • @ClausJørgensen Isn't there a `UIColor(color)` initializer, and then you can get them from the `UIColor`? i.e., https://developer.apple.com/documentation/uikit/uicolor/3550899-init (I haven't tried if calling the `getHue:saturation:brightness:alpha:` on the result does anything sensible, though). – Arkku Nov 27 '21 at 18:53
  • Ah, they added it in iOS14 – Claus Jørgensen Nov 27 '21 at 19:09
  • If you are doing this for darkMode and lightMode then why not use the iOS property of darkMode to do it? Like this … https://stackoverflow.com/questions/57279700/programmatically-detect-dark-mode-in-swiftui-to-display-appropriate-image/57280180#57280180 – Fogmeister Nov 28 '21 at 08:00

3 Answers3

1

You can get the HSB brightness component from Color by first converting it to a UIColor. Here's a little extension I made to do this:

import UIKit
import SwiftUI

extension Color {
    enum Brightness {
        case light, medium, dark, transparent

        private enum Threshold {
            static let transparent: CGFloat = 0.1
            static let light: CGFloat = 0.75
            static let dark: CGFloat = 0.3
        }

        init(brightness: CGFloat, alpha: CGFloat) {
            if alpha < Threshold.transparent {
                self = .transparent
            } else if brightness > Threshold.light {
                self = .light
            } else if brightness < Threshold.dark {
                self = .dark
            } else {
                self = .medium
            }
        }
    }

    var brightness: Brightness {
        var b: CGFloat = 0
        var a: CGFloat = 0
        let uiColor = UIColor(self)
        uiColor.getHue(nil, saturation: nil, brightness: &b, alpha: &a)
        return .init(brightness: b, alpha: a)
    }
}

Color.white.brightness // .light
Color.gray.brightness  // .medium
Color.black.brightness // .dark
Color.clear.brightness // .transparent

The thresholds are ad hoc and untested for any real purpose. The UIColor.init used here also requires iOS 14+ (https://developer.apple.com/documentation/uikit/uicolor/3550899-init).

Arkku
  • 41,011
  • 10
  • 62
  • 84
1

Here is a right way for you:

struct ContentView: View {
    
    @State private var backgroundColor: Color = Color(UIColor.systemBackground)
    @State private var avatarColor: Color = Color.green
    
    var body: some View {
        
        ZStack {
            
            backgroundColor

            VStack(spacing: 20.0) {
                
                Image(systemName: "person")
                    .font(Font.system(size: 100.0))
                    .modifier(AvatarImageModifier(avatarColor: avatarColor) { colorValue in backgroundColor = colorValue })
                
                Button("Set avatarColor to white") { avatarColor = Color.white }
                
                Button("Set avatarColor to black") { avatarColor = Color.black }
                
            }

        }
  
    }
    
}

struct AvatarImageModifier : ViewModifier {
    
    @Environment(\.colorScheme) var colorScheme
    
    let avatarColor: Color
    let backgroundColor: (Color) -> Void
    
    func body(content: Content) -> some View {
        
        content
            .foregroundColor(avatarColor)
            .preference(key: ColorPreferenceKey.self, value: avatarColor)
            .onPreferenceChange(ColorPreferenceKey.self) { newValue in

                if (newValue == Color.black) {
  
                    if (colorScheme == .dark) {
                        backgroundColor(Color(UIColor.systemGray3))
                    }
                    else {
                        backgroundColor(Color(UIColor.systemBackground))
                    }
                    
                }
                else if (newValue == Color.white) {

                    if (colorScheme == .dark) {
                        backgroundColor(Color(UIColor.systemBackground))
                    }
                    else {
                        backgroundColor(Color(UIColor.systemGray3))
                    }
                    
                }
  
            }
            .onChange(of: colorScheme, perform: { newValue in
                
                
                if (avatarColor == Color.black) {
  
                    if (newValue == .dark) {
                        backgroundColor(Color(UIColor.systemGray3))
                    }
                    else {
                        backgroundColor(Color(UIColor.systemBackground))
                    }
                    
                }
                else if (avatarColor == Color.white) {

                    if (newValue == .dark) {
                        backgroundColor(Color(UIColor.systemBackground))
                    }
                    else {
                        backgroundColor(Color(UIColor.systemGray3))
                    }
                    
                }
  
            })

    }
    
}

struct ColorPreferenceKey: PreferenceKey {
    
    static var defaultValue: Color { get { return Color.clear } }
    
    static func reduce(value: inout Color, nextValue: () -> Color) { value = nextValue() }
    
}
ios coder
  • 1
  • 4
  • 31
  • 91
1

In your update you say that the user can select the background colour of their avatar. And you need to add something to it depending on darkMode and lightMode.

So, why not store something when the user selects the background colour?

struct User {
  enum Background {
    case white
    case black

    var color: Color {
      switch self {
      case .white: return .white
      case .black: return .black
      }
    }
  }

  var selectedBG: Background
}

Now you don’t have to worry about detecting what the colour was. You have it in your own enum.

And then you can use the colorScheme environment property from here… Programmatically detect dark mode in SwiftUI to display appropriate Image

To decide what you want to do with the background.

Fogmeister
  • 76,236
  • 42
  • 207
  • 306