The zIndex of a View does not update correctly inside ForEach?

I have some views that should be brought to the top when dragged.
However, while I can confirm via debugger and logging that the zIndex is being updated, the views are never actually reordered.

Here is an example project that shows the issue. The rectangle being dragged should float to the top.

Code Block swift
import SwiftUI
class XRectModel: ObservableObject, Identifiable, Hashable, Equatable {
    var id = UUID()
    static func == (lhs: XRectModel, rhs: XRectModel) -> Bool {
        lhs.id == rhs.id
    }
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
    init(color: Color, order: Double) {
        self.color = color
        self.order = order
    }
    @Published var color: Color
    @Published var order: Double
}
struct XRect: View {
    static var maxorder: Double = 0
    @State var rect: XRectModel
    @State var pos: CGSize = CGSize()
    var drag: some Gesture {
        DragGesture()
            .onChanged { pos in
                self.pos = pos.translation
                XRect.maxorder += 1
                rect.order = XRect.maxorder
                print("Rect: \(rect.color.description), Order: \(rect.order)")
            }
    }
    var body: some View {
        Rectangle()
            .foregroundColor(rect.color)
            .frame(width: 200, height: 200, alignment: .center)
            .offset(pos)
            .gesture(drag)
    }
}
struct ContentViews: View {
    var rects = [XRectModel(color: Color.green, order: 0),
                 XRectModel(color: Color.yellow, order: 0),
                 XRectModel(color: Color.red, order: 0)]
    var body: some View {
        ZStack {
            Color.blue
            ForEach(rects, id: \.self) { rect in
                XRect(rect: rect)
                    .zIndex(rect.order)
            }
        }
    }
}
@main
struct XRectApp: App {
    var body: some Scene {
        WindowGroup {
            ContentViews()
        }
    }
}


Answered by OOPer in 666121022
Unfortunately, SwiftUI cannot detect changes of properties in an Array, when the Element is a reference type.
(Even when you add @State on the Array declaration or you add @Published to the properties.)
So, changes to color or order do not trigger UI updates in ContentViews.

If you make XRectModel struct, and add @State to rects, you will find zIndex works correctly:
Code Block
struct XRectModel: Hashable {
var id = UUID()
var color: Color
var order: Double
}
struct XRect: View {
static var maxorder: Double = 0
@Binding var rect: XRectModel
@State var pos: CGSize = CGSize()
var drag: some Gesture {
DragGesture()
.onChanged { pos in
self.pos = pos.translation
XRect.maxorder += 1
rect.order = XRect.maxorder
print("Rect: \(rect.color.description), Order: \(rect.order)")
}
}
var body: some View {
Rectangle()
.foregroundColor(rect.color)
.frame(width: 200, height: 200, alignment: .center)
.offset(pos)
.gesture(drag)
}
}
struct ContentViews: View {
@State var rects = [XRectModel(color: Color.green, order: 0),
XRectModel(color: Color.yellow, order: 0),
XRectModel(color: Color.red, order: 0)]
var body: some View {
ZStack {
Color.blue
ForEach(rects.indices, id: \.self) { index in
XRect(rect: $rects[index])
.zIndex(rects[index].order)
}
}
}
}


Accepted Answer
Unfortunately, SwiftUI cannot detect changes of properties in an Array, when the Element is a reference type.
(Even when you add @State on the Array declaration or you add @Published to the properties.)
So, changes to color or order do not trigger UI updates in ContentViews.

If you make XRectModel struct, and add @State to rects, you will find zIndex works correctly:
Code Block
struct XRectModel: Hashable {
var id = UUID()
var color: Color
var order: Double
}
struct XRect: View {
static var maxorder: Double = 0
@Binding var rect: XRectModel
@State var pos: CGSize = CGSize()
var drag: some Gesture {
DragGesture()
.onChanged { pos in
self.pos = pos.translation
XRect.maxorder += 1
rect.order = XRect.maxorder
print("Rect: \(rect.color.description), Order: \(rect.order)")
}
}
var body: some View {
Rectangle()
.foregroundColor(rect.color)
.frame(width: 200, height: 200, alignment: .center)
.offset(pos)
.gesture(drag)
}
}
struct ContentViews: View {
@State var rects = [XRectModel(color: Color.green, order: 0),
XRectModel(color: Color.yellow, order: 0),
XRectModel(color: Color.red, order: 0)]
var body: some View {
ZStack {
Color.blue
ForEach(rects.indices, id: \.self) { index in
XRect(rect: $rects[index])
.zIndex(rects[index].order)
}
}
}
}


The zIndex of a View does not update correctly inside ForEach?
 
 
Q