skip to Main Content

I want to use a SwiftUI Picker to select different colors. Instead of using Text() for each item in a picker, I want to use a Rectangle() to visually show the color, but SwiftUI doesn’t like it and shows me a bunch of blank spaces. Here are my implementations:

struct TeamColorPicker: View {
    @State var teamColor: ColorSquare = ColorSquare(color: .blue)
    var listOfColorsAvailable: [String] = ["Red", "Blue", "Teal", "Green", "Yellow", "Orange"]
        
    var body: some View {
        Picker("Team Color", selection: $teamColor) {
            ForEach(listOfColorsAvailable, id: .self) { colorString in
                let color = Color(colorString)
                ColorSquare(color: color)
                    .tag(color)
            }
        }
    }
}

and

struct ColorSquare: View, Hashable {
    var color: Color
    
    static func == (lhs: ColorSquare, rhs: ColorSquare) -> Bool {
        return lhs.color == rhs.color
    }


    func hash(into hasher: inout Hasher) {
        hasher.combine(color)
    }
    
    var body: some View {
        Rectangle().fill(color).aspectRatio(1.0, contentMode: .fill).scaleEffect(0.025)
    }
}

Thank you in advance for any guidance!!

I read somewhere that the Picker expects the selection element to conform to Hashable, so I made the ColorSquare struct for this reason.

2

Answers


  1. It seems like the issue might be with using a custom type (ColorSquare) as the selection type for the Picker. The selection type must conform to the Hashable and Equatable protocols. In your case, ColorSquare conforms to Hashable, but you also need to make it conform to Equatable.

    Here’s the modified ColorSquare struct:

    struct ColorSquare: View, Hashable, Equatable {
        var color: Color
        
        static func == (lhs: ColorSquare, rhs: ColorSquare) -> Bool {
            return lhs.color == rhs.color
        }
    
        func hash(into hasher: inout Hasher) {
            hasher.combine(color)
        }
        
        var body: some View {
            Rectangle().fill(color).aspectRatio(1.0, contentMode: .fill).scaleEffect(0.025)
        }
    }
    
    

    Now, try updating your TeamColorPicker as follows:

    struct TeamColorPicker: View {
        @State private var selectedColor: Color = .blue
        var listOfColorsAvailable: [String] = ["Red", "Blue", "Teal", "Green", "Yellow", "Orange"]
        
        var body: some View {
            Picker("Team Color", selection: $selectedColor) {
                ForEach(listOfColorsAvailable, id: .self) { colorString in
                    let color = Color(colorString)
                    ColorSquare(color: color)
                        .tag(color)
                }
            }
        }
    }
    
    

    P.S: I hope that if this is not correct, it at least helps you to find direction, all the best!

    Login or Signup to reply.
  2. Picker has a number of limitations about what it can display.

    The following example code shows how to display the listOfColorsAvailable
    but only with the .wheel and .inline style picker.

    Note the selection teamColor, must match the type of the tag() of the picker elements.

    struct ContentView: View {
        var body: some View {
            TeamColorPicker()
        }
    }
    
    struct TeamColorPicker: View {
        @State private var teamColor: String = "Blue" // <--- here, same type as tag()
    
        var listOfColorsAvailable: [String] = ["Red", "Blue", "Teal", "Green", "Yellow", "Orange"]
            
        var body: some View {
            Picker("Team Color", selection: $teamColor) {
                ForEach(listOfColorsAvailable, id: .self) { colorString in
                    ColorSquare(color: asColor(colorString))
                        .tag(colorString) // <--- here, same type as teamColor
                }
            }
            .pickerStyle(.wheel) // <--- here, .wheel, .inline
        }
        
        // for testing, can use Assets.xcassets
        func asColor( _ color: String) -> Color {
            switch color {
                case "Red": Color.red
                case "Blue": Color.blue
                case "Teal": Color.teal
                case "Green": Color.green
                case "Yellow": Color.yellow
                case "Orange": Color.orange
                default: Color.black
            }
        }
    }
    
    struct ColorSquare: View {
        var color: Color
    
        var body: some View {
            Rectangle().fill(color).frame(width: 22, height: 22)
        }
    }
    

    Alternatively, using Color directly:

    struct TeamColorPicker: View {
        @State private var teamColor: Color = Color.blue // <--- here, same type as tag()
    
        let listOfColorsAvailable: [Color] = [.red, .blue, .teal, .green, .yellow, .orange]
            
        var body: some View {
            teamColor.frame(width: 22, height: 22) // <-- for testing
            Picker("Team Color", selection: $teamColor) {
                ForEach(listOfColorsAvailable, id: .self) { color in
                    ColorSquare(color: color)
                        .tag(color) // <--- here, same type as teamColor
                }
            }
            .pickerStyle(.wheel) // <--- here, .wheel, .inline
        }
    
    }
    
    struct ColorSquare: View {
        var color: Color
    
        var body: some View {
            Rectangle().fill(color).frame(width: 22, height: 22)
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search