Search code examples
swiftswiftuiswiftui-view

Passing calculated variable to another View


I am trying to create my own grid, which resizing to every element. It's okay with that. Here is a code

GeometryReader { geo in
            let columnCount = Int((geo.size.width / 250).rounded(.down))
            let tr = CDNresponse.data?.first?.translations ?? []
            let rowsCount = (CGFloat(tr.count) / CGFloat(columnCount)).rounded(.up)
            LazyVStack {
                ForEach(0..<Int(rowsCount), id: \.self) { row in // create number of rows
                    HStack {
                        ForEach(0..<columnCount, id: \.self) { column in // create columns
                            let index = row * columnCount + column
                            if index < (tr.count) {
                                VStack {
                                    MovieCellView(index: index, tr: tr, qualities: $qualities)
                                }
                            }
                        }
                    }
                }
            }
        }

But since I don't know the exact number of elements and their indices, I need to calculate them in view.

let index = row * columnCount + column

And that's the problem - if I pass them as usual (MovieCellView(index: index ... )) when the index changing, the new value is not passed to view. I cannot use @State and @Binding, as I cannot declare it directly in View Builder and can't declare it on struct because I don't know the count. How to pass data correctly?

Code of MovieCellView:

struct MovieCellView: View {
    @State var index: Int
    @State var tr: [String]
    @State var showError: Bool = false
    @State var detailed: Bool = false
    
    @Binding var qualities: [String : [Int : URL]]
    
    var body: some View {
        ...
    }
    
}

The most simple example Just added Text("\(index)") in VStack with MovieCellView and Text("\(index)") in MovieCellView body. Index in VStack always changing, but not in MovieCellView. Result It is necessary to clarify, my application is on a macOS and the window is resized


Solution

  • Since none of the answers worked, and only one worked half, I solved the problem myself. You need to generate a Binding, and then just pass it

    var videos: some View {
            GeometryReader { geo in
                let columnCount = Int((geo.size.width / 250).rounded(.down))
                let rowsCount = (CGFloat((CDNresponse.data?.first?.translations ?? []).count) / CGFloat(columnCount)).rounded(.up)
                LazyVStack {
                    ForEach(0..<Int(rowsCount), id: \.self) { row in // create number of rows
                        HStack {
                            ForEach(0..<columnCount, id: \.self) { column in // create 3 columns
                                let index = row * columnCount + column
                                if index < ((CDNresponse.data?.first?.translations ?? []).count) {
                                    MovieCellView(translation: trBinding(for: index), qualities: qualityBinding(for: (CDNresponse.data?.first?.translations ?? [])[index]))
                                }
                            }
                        }
                    }
                }
            }
        }
        private func qualityBinding(for key: String) -> Binding<[Int : URL]> {
            return .init(
                get: { self.qualities[key, default: [:]] },
                set: { self.qualities[key] = $0 })
        }
        private func trBinding(for key: Int) -> Binding<String> {
            return .init(
                get: { (self.CDNresponse.data?.first?.translations ?? [])[key] },
                set: { _ in return })
        }
    
    
    
    struct MovieCellView: View {
        @State var showError: Bool = false
        @State var detailed: Bool = false
        @Binding var translation: String
        
        @Binding var qualities: [Int : URL]
        
        var body: some View {
            ...
        }
        
    }