Передача вычисляемой переменной в другое представление

Я пытаюсь создать свою собственную сетку, размер которой изменяется под каждый элемент. Это нормально. Вот код

      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 3 columns
                            let index = row * columnCount + column
                            if index < (tr.count) {
                                VStack {
                                    MovieCellView(index: index, tr: tr, qualities: $qualities)
                                }
                            }
                        }
                    }
                }
            }
        }

Но поскольку я не знаю точное количество элементов и их индексы, мне нужно их вычислить с учетом.

      let index = row * columnCount + column

И вот в чем проблема - если я их передам как обычно ( MovieCellView(index: index ... )) при изменении индекса новое значение не передается для просмотра. Я не могу использовать @State и @Binding, так как я не могу объявить его непосредственно в View Builder и не могу объявить его в структуре, потому что я не знаю счетчика. Как правильно передавать данные?

Код:

      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 {
        ...
    }
    
}

Самый простой пример, только что добавленный в VStack с помощью MovieCellView и Text("\(index)") в MovieCellViewтело. Индекс в VStack всегда меняется, но не в MovieCellView. Надо уточнить, у меня приложение на macOS и размер окна изменен

4 ответа

Это мое решение (тестовый код) вашей проблемы передачи вычисляемой переменной в другое представление. Я использую ObservableObject для хранения информации, необходимой для достижения того, что вам нужно.

      import SwiftUI

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class MovieCellModel: ObservableObject {
    @Published var columnCount: Int = 0
    @Published var rowCount: Int = 0
    
    // this could be in the view 
    func index(row: Int, column: Int) -> Int {
        return row * columnCount + column
    }
}

struct ContentView: View {
    @StateObject var mcModel = MovieCellModel()
    @State var qualities: [String : [Int : URL]] = ["":[1: URL(string: "https://example.com")!]] // for testing
    let tr = CDNresponse.data?.first?.translations ?? [] // for testing
    
    var body: some View {
        VStack {
            GeometryReader { geo in
                LazyVStack {
                    ForEach(0..<Int(mcModel.rowCount), id: \.self) { row in // create number of rows
                        HStack {
                            ForEach(0..<mcModel.columnCount, id: \.self) { column in // create 3 columns
                                if mcModel.index(row: row, column: column) < (tr.count) {
                                    VStack {
                                        MovieCellView(index: mcModel.index(row: row, column: column), tr: tr, qualities: $qualities) 
                                    }
                                }
                            }
                        }
                    }
                }.onAppear {
                    mcModel.columnCount = Int((geo.size.width / 250).rounded(.down))
                    mcModel.rowCount = Int((CGFloat(tr.count) / CGFloat(mcModel.columnCount)).rounded(.up))
                }
            }
        }
        
    }
}

struct MovieCellView: View {
    @State var index: Int

    @State var tr: [String]
    @Binding var qualities: [String : [Int : URL]]
    
    @State var showError: Bool = false
    @State var detailed: Bool = false

    var body: some View {
        Text("\(index)").foregroundColor(.red)
    }
}

Поскольку ни один из ответов не помог, а только один сработал наполовину, я решил проблему сам. Вам нужно создать привязку, а затем просто передать ее

      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 {
        ...
    }
    
}

Если ваша коллекция изменится, ваше представление должно быть пересчитано, и старое представление со старым индексом будет отклонено. Как может измениться индекс? Объясните, пожалуйста, что именно не работает?

CDNresponse.data?.first?.translationsэто похоже на синглтон. Если вы обновите его значение и ожидаете, что ваше представление тоже будет обновлено, этого не произойдет.

Единственная переменная, которая изменяется за пределами ForEach в вашей index расчет есть. row и column- это просто индексы из циклов. Таким образом, вы можете объявить @State переменная в родительском представлении, поэтому, когда она изменяется, родительское представление обновится, и, следовательно, все ячейки также обновятся с новым columnCount.

      @State private var columnCount: CGFloat = 0

var body: some View {
    GeometryReader { geo in
        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 3 columns
                        let index = row * columnCount + column
                        if index < (tr.count) {
                            VStack {
                                MovieCellView(index: index, tr: tr, qualities: $qualities)
                            }
                        }
                    }
                }
            }
        }
    }
}
Другие вопросы по тегам