简体   繁体   中英

ForEach inside HStack causes size to be zero

I was playing with a SwiftUI animation that required the view size. In my case, it is some list implemented using HStack. Since it consists of many items, I used the ForEach struct.

It shocked me when I saw its size (0.0, 0.0). After digging, I found out that changing ForEach to an explicit items declaration made the size correct.

And now, there's a question – why is that? How do you suggest overcoming this issue?

For me, it looks like a SwiftUI bug. Since ForEach conforms to the View protocol, it should behave like any other view IMO:D

Below you can find snippets of the code related to the issue. Both ForEach implementations and size modifier-related.

struct ContentView: View {
    @State private var contentSize: CGSize = .zero

    var body: some View {
        HStack {
            HStack {
                // Version 1: - With ForEach inside HStack's size is (0.0, 0.0)
                ForEach([0, 1], id: \.self) { item in
                    Text("Item \(item)")
                }
                // Version 2: - Without ForEach inside HStack's size is (101.66666666666666, 20.333333333333332)
                Text("Item 0")
                Text("Item 1")
            }
            .fixedSize()
            .size($contentSize)
        }
    }
}
extension View {
    public func size(_ size: Binding<CGSize>) -> some View {
        modifier(SizeBindingViewModifier(size: size))
    }
}
struct SizePreferenceKey: PreferenceKey {
    typealias Value = CGSize
    static var defaultValue: Value = .zero

    static func reduce(value: inout Value, nextValue: () -> Value) {
        value = nextValue()
    }
}
struct SizeBindingViewModifier: ViewModifier {
    @Binding private var size: CGSize

    init(size: Binding<CGSize>) {
        _size = size
    }

    func body(content: Content) -> some View {
        content
            .background(
                GeometryReader { proxy in
                    Color
                        .clear
                        .preference(
                            key: SizePreferenceKey.self,
                            value: proxy.size
                        )
                }
            )
            .onPreferenceChange(SizePreferenceKey.self) { size in
                print("size: \(size)")
                self.size = size
            }
    }
}

Interestingly, if you check value and nextValue() in reduce , value is initially set correctly.

In fact, in the example above, reduce is only called (with nextValue() =.zero ) when the ForEach is used. Presumably this is due size not being calculable initially due to the "dynamic" nature of the ForEach .

Assuming your views will never have zero size, a simple workaround could be:

static func reduce(value: inout Value, nextValue: () -> Value) {
    guard nextValue() != .zero else { return }
    value = nextValue()
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM