BLACK FRIDAY: Save 50% on all books and bundles! >>

SwiftUI Changing buttons color inside forEach

Forums > SwiftUI

I want to change color of buttons when clicked inside forEach statement. I want to apply different color for different button. Can anyone help me out.

Thanks in advance

   

@twostraws  Site AdminHWS+

If you use ForEach with a range such as 0..<5, you'll be handed one value from that range each time the loop goes around. You can then use that to read one item from your array, and one color from a colors array you defined previously.

   

@twostraws, Here is my code:

import SwiftUI

struct ColorModel: Identifiable {
    let value: Color
    let id = UUID()
}
let colors = [
    ColorModel(value: Color.orange),
    ColorModel(value: Color.green),
    ColorModel(value: Color.blue),
    ColorModel(value: Color.red),
    ColorModel(value: Color.yellow),
    ColorModel(value: Color.gray),
    ColorModel(value: Color.pink),
]
let totalButtons: Int = 10

struct ContentView: View {
    func updateSelectedButtons(value: Int) {
        if self.selectedButtons.contains(value) {
            if let index = self.selectedButtons.firstIndex(of: value) {
                self.selectedButtons.remove(at: index)
            }
        } else {
            if self.selectedButtons.count < 7 {
                self.selectedButtons.append(value)
            }
        }
    }
    @State private var selectedButtons: [Int] = [Int]()
    @State private var colorIndex: Int = 0
    var body: some View {
        ForEach(0 ..< totalButtons) { index in
            Button(action: {
                self.updateSelectedButtons(value: index)
                self.colorIndex += 1
            }) {
                Text("  ")
            }
            .background(self.selectedButtons.contains(index) ? colors[self.colorIndex].value : Color.white)
        }
    }
}

Actually i want to change color the buttons as colors array above. For eg: if a user first selects any button then the color of that button should be organe, and if the user selects another button then it should be green and so on. The user can select upto 7 buttons and if 7 different buttons are selected, then they should have 7 different color. The problem in the code above is that if a user first selects any button, then color of that button is orange and if selects 2nd button, then both becomes green. Thanks :)

   

Ok so the issue is that you have a single array to store ALL the buttons that are currently 'on' and you are setting the SAME color for all of them.

 .background(self.selectedButtons.contains(index) ? colors[self.colorIndex].value : Color.white)

That basically says for ALL of the button in the selectedButtons array - color them the current colorIndex - or color them white.

So what you need is to have an array of 'Buttons' which have their OWN color associated with them

For example

struct ColorButton
{
    let color: Color
    let buttonIndex: Int
}

In you view you can then create a State which will hold onto them

 @State private var buttonList = [ColorButton]()

And update your updateSelectedButtons func to use that instead

func updateSelectedButtons(value: Int)
    {
        if self.buttonList.contains(where: { $0.buttonIndex == value } )
        {
            self.buttonList.remove(at: self.buttonList.firstIndex(where: {$0.buttonIndex == value})!)
        }
        else
        {
            self.buttonList.append(ColorButton(color: colors[self.colorIndex].value, buttonIndex: value))
            self.colorIndex += 1
            if self.colorIndex >= 7
            {
                self.colorIndex = 0
            }
        }
    }

So here we are saying that IF we have a button with the selected index then REMOVE it from the array Otheriwse APPEND it to the list and set the Color of THAT button to the current colorIndex and then increment the colorIndex - rotate if too large.

Then your body for the view is simpler too - we have a helper function (really should be a custom modifier - but hey I was rushed)

 func getColor(_ value: Int) -> Color
    {
        return self.buttonList.first(where: { $0.buttonIndex == value })?.color ?? Color.white
    }

This will return the color from the struct in the array OR White

var body: some View {
        VStack
        {
            ForEach(0 ..< totalButtons) { index in
                Button(action: {
                    self.updateSelectedButtons(value: index)
                }) {
                    Text("Button \(index)")
                    .padding()
                }
                .background(self.getColor(index))
                .overlay(
                RoundedRectangle(cornerRadius: 20)
                    .stroke(Color.purple, lineWidth: 5) )
                .padding()
            }
        }
    }

so full code is

import SwiftUI

struct ColorModel: Identifiable {
    let value: Color
    let id = UUID()
}
let colors = [
    ColorModel(value: Color.orange),
    ColorModel(value: Color.green),
    ColorModel(value: Color.blue),
    ColorModel(value: Color.red),
    ColorModel(value: Color.yellow),
    ColorModel(value: Color.gray),
    ColorModel(value: Color.pink),
]
let totalButtons: Int = 6

struct ColorButton
{
    let color: Color
    let buttonIndex: Int
}

struct ContentView: View
{
    func updateSelectedButtons(value: Int)
    {
        if self.buttonList.contains(where: { $0.buttonIndex == value } )
        {
            self.buttonList.remove(at: self.buttonList.firstIndex(where: {$0.buttonIndex == value})!)
        }
        else
        {
            self.buttonList.append(ColorButton(color: colors[self.colorIndex].value, buttonIndex: value))
            self.colorIndex += 1
            if self.colorIndex >= 7
            {
                self.colorIndex = 0
            }
        }
    }

    func getColor(_ value: Int) -> Color
    {
        return self.buttonList.first(where: { $0.buttonIndex == value })?.color ?? Color.white
    }

    @State private var buttonList = [ColorButton]()

    @State private var selectedButtons: [Int] = [Int]()
    @State private var colorIndex: Int = 0

    init()
    {

    }

    var body: some View {
        VStack
        {
            ForEach(0 ..< totalButtons) { index in
                Button(action: {
                    self.updateSelectedButtons(value: index)
                }) {
                    Text("Button \(index)")
                    .padding()
                }
                .background(self.getColor(index))
                .overlay(
                RoundedRectangle(cornerRadius: 20)
                    .stroke(Color.purple, lineWidth: 5) )
                .padding()
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

I am sure that @twostraws will have a cleaner way of doing it but this works at least

   

Save 50% in my Black Friday sale.

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

Not logged in

Log in
 

Link copied to your pasteboard.