0

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.

3 Answers 3

1

If you are looking to use a Picker to select a Color then it can be done by converting each Color to an Image. Each image can then be used to create a Label.

Like this:

struct TeamColorPicker: View {
    @State var teamColor: Color = .red
    var listOfColorsAvailable: [String] = ["Red", "Blue", "Teal", "Green", "Yellow", "Orange"]

    private func colorImage(color: Color) -> Image {
        Image(size: CGSize(width: 26, height: 20)) { ctx in // includes trailing padding
            ctx.fill(
                Path(
                    roundedRect: CGRect(origin: .zero, size: CGSize(width: 20, height: 20)),
                    cornerRadius: 3
                ),
                with: .color(color)
            )
        }
    }

    var body: some View {
        Picker("Team Color", selection: $teamColor) {
            ForEach(listOfColorsAvailable, id: \.self) { colorString in
                let color = Color(colorString)
                Label(
                    title: { Text(colorString) },
                    icon: { colorImage(color: color) }
                )
                .tag(color)
            }
        }
    }
}

Screenshot

Ps. I used the same code to convert a String to a Color as you were using, but for this to work, you need to add colors with these exact names to your asset catalog. You could also add images with these exact names to your asset catalog too, then you could use them as label images directly.

Sign up to request clarification or add additional context in comments.

Comments

1

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)
    }
}

Comments

0

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!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.