Search code examples
iosswiftswiftuipicker

SwiftUI Picker issue with Rectangle() instead of Text()


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.


Solution

  • 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.