#swift #swiftui #swiftui-view
Вопрос:
Я пытаюсь создать свою собственную сетку, размер которой изменяется для каждого элемента. С этим все в порядке. Вот код
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)
}
}
}
}
}
}
}
Но поскольку я не знаю точного количества элементов и их индексов, мне нужно рассчитать их с учетом.
let index = row * columnCount column
И в этом проблема — если я передам их как обычно ( MovieCellView(index: index ... )
) при изменении индекса, новое значение не будет передано для просмотра.
Я не могу использовать @State и @Binding, так как я не могу объявить его непосредственно в конструкторе представлений и не могу объявить его в структуре, потому что я не знаю количество. Как правильно передавать данные?
Кодекс 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 {
...
}
}
Самый простой пример
Просто добавлено Text("(index)")
в VStack с MovieCellView и Text("(index)")
в MovieCellView
теле. Индекс в VStack всегда меняется, но не в MovieCellView.
Необходимо уточнить, что мое приложение находится на macOS, и размер окна изменен
Ответ №1:
Это мое решение (тестовый код) для вашей проблемы передачи вычисляемой переменной в другое представление. Я использую ObservableОбъект для хранения информации, необходимой для достижения того, что вам нужно.
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)
}
}
Комментарии:
1. У меня один и тот же индекс для каждой ячейки, использующей это решение
2. да, извините, я этого не видел. Я обновил свой ответ.
3. Это избавляет меня от главного преимущества моей сетки — замены элементов и динамического изменения размера.
4. означает ли это, что это не работает для вас?
5. Да, потому что количество столбцов и строк не меняются
Ответ №2:
Единственная переменная, которая изменяется за пределами a ForEach
в вашем index
расчете, — это columnCount
. row
и column
являются просто индексами из циклов. Таким образом, вы можете объявить columnCount
как @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)
}
}
}
}
}
}
}
}
Комментарии:
1. Переменные состояния не могут быть изменены в представлении, поэтому это не работает
2. @Higherous вы действительно пробовали этот код? Переменные состояния предназначены для изменения из тела представления, что является их основной целью. Они не могут быть изменены извне представления, но они могут быть изменены изнутри него.
3. Ты действительно уверен? Просто попробуйте минимальный легкий код с тем же использованием
struct ContentView: View { @State var myVar: Int = 0 var body: some View { myVar = 5 Text("Hello, World!") .padding() } }
Переменные состояния могут быть изменены только с помощью таких функций, как onAppear4. @Higherous это совершенно неправильно, не знаю, откуда у тебя эта идея. Я полностью уверен, что изменение
State
переменных внутри тела представления безопасно.5. Это невозможно. Я сбросил код выше, попробуйте сами Вставить pastebin.com/qiQ1Y79z
Ответ №3:
Поскольку ни один из ответов не сработал, и только один сработал наполовину, я решил проблему сам. Вам нужно сгенерировать привязку, а затем просто передать ее
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 {
...
}
}