0

The following code successfully shows a set of colors and displays the selected color when the colors are tapped, similar to the standard ColorPicker. What I would like to be able to do is use this custom color picker in other views, in a similar way as the standard ColorPicker. My issue is that I cannot expose the selected color to other views.

ContentView:

    struct ContentView: View {
        var body: some View {
            VStack{
            CustomColorPicker()
            }
        }
    }

Custom Color Picker:

    struct CustomColorPicker: View {
        var colors: [UIColor] = [.red, .green, .blue, .purple, .orange]
        
        @State var selectedColor = UIColor.systemGray6

        var body: some View {
            VStack {
                Rectangle()
                    .fill(Color(selectedColor))
                    .frame(width: 45, height: 45)
                
                HStack(spacing: 0) {
                    ForEach(colors, id: \.self) { color in
                        Button {
                            selectedColor = color
                        } label: {
                            Color(color)
                                .border(Color.gray, width: color == selectedColor ? 2 : 0)
                        }
                    }
                }
                .frame(height: 50.0)
            }
        }
    }

I have tied using a model/ObservableObject to be able to capture the selected color in other views but it doesn't when you select the colors.

How can I make the Rectangle in ContentView update its fill color when a color in the color picker is tapped?

Or in general, what would be the best way to create a reusable custom color picker?

Using an ObservableObject

Content View

    struct ContentView: View {
        
        @ObservedObject var cModel = ColorPickerModel()
        
        var body: some View {
            VStack{
            CustomColorPicker()
                
                Rectangle()
                    .fill(cModel.selectedColor)
                    .frame(width: 100, height: 100)
            }
        }
    }

Custom Color Picker

    class ColorPickerModel:ObservableObject{
        @Published var selectedColor:Color = Color.orange
    }

    struct CustomColorPicker: View {
        var colors: [UIColor] = [.red, .green, .blue, .purple, .orange]
        
        @StateObject var cModel = ColorPickerModel()

        var body: some View {
            VStack {
                Rectangle()
                    .fill(cModel.selectedColor)
                    .frame(width: 45, height: 45)
                
                HStack(spacing: 0) {
                    ForEach(colors, id: \.self) { color in
                        Button {
                            cModel.selectedColor = Color(color)
                        } label: {
                            Color(color)
                                //.border(Color.gray, width: color == selectedColor ? 2 : 0)
                        }
                    }
                }
                .frame(height: 50.0)
            }
        }
    }
fs_tigre
  • 10,650
  • 13
  • 73
  • 146
  • 1
    In your first example. Make State a Binding for reusability. Then store the value like anything else you are storing so you can access it anywhere. – lorem ipsum Nov 29 '21 at 19:40
  • @loremipsum - Would you mind elaborating more on `@Binding`? Sorry but I don't have too much experience with `@Binding` or SwiftUI in general. Thanks – fs_tigre Nov 29 '21 at 19:57

1 Answers1

1
import SwiftUI

struct MyColorView: View {
    //Source of truth
    @StateObject var cModel = MyColorViewModel()
    
    var body: some View {
        VStack{
            CustomColorPicker(selectedColor: $cModel.selectedColor)
            
            Rectangle()
            //Convert to the View Color
                .fill(Color(cModel.selectedColor))
                .frame(width: 100, height: 100)
        }
    }
}
//This will serve as the source of truth for this View and any View you share the ObservableObject with
//Share it using @ObservedObject and @EnvironmentObject
class MyColorViewModel:ObservableObject{
    //Change to UIColor the types have to match
    @Published var selectedColor: UIColor = .orange
}
struct CustomColorPicker: View {
    var colors: [UIColor] = [.red, .green, .blue, .purple, .orange]
    //You can now use this Picker with any Model
    //Binding is a two-way connection it needs a source of truth
    @Binding var selectedColor: UIColor
    
    var body: some View {
        VStack {
            HStack(spacing: 0) {
                ForEach(colors, id: \.self) { color in
                    Button {
                        selectedColor = color
                    } label: {
                        Color(color)
                            .border(Color.gray, width: color == selectedColor ? 2 : 0)
                    }
                }
            }
            .frame(height: 50.0)
        }
    }
}
struct MyColorView_Previews: PreviewProvider {
    static var previews: some View {
        MyColorView()
    }
}

Try the Apple SwiftUI Tutorials they are a good start.

lorem ipsum
  • 21,175
  • 5
  • 24
  • 48
  • It worked. I will try to learn more about `@Binding`, thanks a lot! – fs_tigre Nov 29 '21 at 20:22
  • As I said, it worked find but I'm having a hard time the connection between `@Published var selectedColor: UIColor = .orange` and `@Binding var selectedColor: UIColor`, I changed the name for the `@Binding var` and it still works. Where does the connection between the two variables happen? At what point is the `@Published var` updated to be the selected color? Sorry, but I cannot quite understand the relation between the two property wrappers. – fs_tigre Nov 30 '21 at 02:05
  • $fs_tigre the connection happens where you see the $ in MyColorView. The view model connects with the Binding there. Then when you tap the color you set the variable – lorem ipsum Nov 30 '21 at 11:21