NEW: My new book Pro SwiftUI is out now – level up your SwiftUI skills today! >>

SOLVED: ForEach bindings

Forums > SwiftUI

The following code creates a table-type structure, and the goal is for a particular, tapped box (not all the boxes) in the grid to change from an empty rectangle with a red border to a rectangle containing text with a white/black border. I could swear I got this to work before I slimmed down the code and moved the ZStack{}s into a separate struct. Then I tried to reverse engineer the original code because I forgot to commit it. But maybe I was hallucinating. πŸ˜΅β€πŸ’« Right now, tapping a box correctly displays text in only that box, but all the borders are changing from red to white/black.

(Item is a struct, with an embedded struct Box; subarray1 and subarray2 are each an Array<Box> inside Item.)

Was I hallucinating? Or am I missing/misplacing a key word somewhere? What appears below should be identical or nearly identical to the original code I had. It has to be somewhere in the commented lines, as those are what had to be adjusted when I moved the ZStack to its own struct.

struct LowerPane: View {
    @State var currentItem: Item
    @State private var borderColor = Color.red  // changed

    var body: some View {
        GeometryReader { ruler in
            ScrollView {
                HStack(spacing: 0) {
                    VStack(spacing: 0) {
                        ForEach($currentItem.subarray1) { $box in
                            ZStack {
                                Rectangle()
                                    .fill(.primary).colorInvert()
                                    .frame(width: ruler.size.width * 0.5, height: 60, alignment: .center)
                                    .border(borderColor)
                                    .onTapGesture(perform: {
                                        box.selected = true  // changed
                                        borderColor = Color.primary  // changed
                                    })
                                if box.selected {  // changed
                                    MyText(str: box.statement)  // changed
                                }
                            }
                        }
                    }

                    VStack(spacing: 0) {
                        ForEach($currentItem.subarray2) { $step in
                            ZStack {
                                Rectangle()
                                    .fill(.primary).colorInvert()
                                    .frame(width: ruler.size.width * 0.5, height: 60, alignment: .center)
                                    .border(borderColor)
                                    .onTapGesture(perform: {
                                        box.selected = true  // changed
                                        borderColor = Color.primary  // changed
                                    })
                                if box.selected {  // changed
                                    MyText(str: box.statement)  // changed
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

   

All of your boxes have a border color modifier controlled by the same property:

.border(borderColor)

So if you change borderColor for one box then all of them will change color.

You should do something like this instead:

.border(box.selected ? .red : .primary)

   

Ah, okay--thanks!

Did my "compacted" version (below) work (changing the color of the tapped box only) because the ForEach creates x number of LowerBox views (and each view has its own borderColor), whereas my "uncompacted" version creates x number of Rectangle shapes?

Either way, I can eliminate the need for the borderColor variable thanks to your advice, @roosterboy. 🌟

struct LowerPane: View {
    @State var currentItem: Item

    var body: some View {
        GeometryReader { ruler in
            ScrollView {
                HStack(spacing: 1) {
                    VStack(spacing: 1) {
                        ForEach($currentItem.subarray1) { $box in
                            LowerBox(r: ruler, s: box)
                        }
                    }

                    VStack(spacing: 1) {
                        ForEach($currentItem.subarray2) { $box in
                            LowerBox(r: ruler, s: box)
                        }
                    }

                }
            }
        }
    }
}

struct LowerBox: View {
    var r: GeometryProxy
    @State var s: Item.Box

    @State private var borderColor = Color.red

    var body: some View {
        ZStack {
            Rectangle()
                .fill(.primary).colorInvert()
                .frame(width: r.size.width * 0.5, height: 60, alignment: .center)
                .border(borderColor)
                .onTapGesture(perform: {
                    s.selected = true
                    borderColor = Color.primary
                })
            if s.selected {
                MyText(str: s.statement)
            }
        }
    }
}

   

Hacking with Swift is sponsored by Play

SPONSORED Play is the first native iOS design tool created for designers and engineers. You can install Play for iOS and iPad today and sign up to check out the Beta of our macOS app with SwiftUI code export. We're also hiring engineers!

Click to learn more about Play!

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

You are not logged in

Log in or create account
 

Link copied to your pasteboard.