ScrollView некорректно работает с GeometryReader - PullRequest
1 голос
/ 05 мая 2020

У меня проблемы с просмотром прокрутки и GeometryReader. Я хочу иметь список предметов под изображением. И каждый предмет должен иметь следующие ширину и высоту: ((width of the entire screen - leading padding - trailing padding) / 2). Я пробовал два подхода для своего варианта использования. Это структура кода моего первого стека:

Подход № 1

ScrollView
   - VStack
      - Image
      - GeometryReader
         - ForEach
            - Text

Я использую считыватель геометрии, чтобы получить ширину VStack, поскольку он имеет отступы, а я не Я хочу иметь всю ширину прокрутки.

Но с GeometryReader в пользовательском интерфейсе отображается только последний элемент из ForEach l oop. А у GeometryReader только небольшая высота. См. Снимок экрана.

Код:

import SwiftUI

struct Item: Identifiable {
    var id = UUID().uuidString
    var value: String
}

struct ContentView: View {

    func items() -> [Item] {
        var items = [Item]()
        for i in 0..<100 {
            items.append(Item(value: "Item #\(i)"))
        }
        return items
    }

    var body: some View {
        ScrollView {
            VStack {
                Image(systemName: "circle.fill")
                    .resizable()
                    .scaledToFill()
                    .frame(width: 150, height: 150)
                    .padding()
                    .foregroundColor(.green)

                GeometryReader { geometry in
                    ForEach(self.items()) { item in
                        Text(item.value)
                            .frame(width: geometry.size.width / CGFloat(2), height: geometry.size.width / CGFloat(2))
                            .background(Color.red)
                    }
                }
                .background(Color.blue)
            }
            .frame(maxWidth: .infinity)
            .padding()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Красный цвет - это элементы в ForEach l oop. Синий цвет для GeometryReader и зеленый только изображение.

Screenshot

Подход №2

ScrollView
   -GeometryReader
      - VStack
         - Image
         - ForEach
            -Text

Тогда элементы в моем ForEach l oop отображаются правильно, но прокручивать больше нельзя.

Код

import SwiftUI

struct Item: Identifiable {
    var id = UUID().uuidString
    var value: String
}

struct ContentView: View {

    func items() -> [Item] {
        var items = [Item]()
        for i in 0..<100 {
            items.append(Item(value: "Item #\(i)"))
        }
        return items
    }

    var body: some View {
        ScrollView {
            GeometryReader { geometry in
                VStack {
                    Image(systemName: "circle.fill")
                        .resizable()
                        .scaledToFill()
                        .frame(width: 150, height: 150)
                        .padding()
                        .foregroundColor(.green)

                    ForEach(self.items()) { item in
                        Text(item.value)
                            .frame(width: geometry.size.width / CGFloat(2), height: geometry.size.width / CGFloat(2))
                            .background(Color.red)
                    }
                }
                .frame(maxWidth: .infinity)
            }
            .padding()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

screenshotNo2

Как заархивировать, чтобы пользовательский интерфейс отображался правильно. Я что-то здесь упускаю? Я был бы очень признателен за помощь.
Спасибо.

РЕДАКТИРОВАТЬ

Я нашел обходной путь, чтобы пользовательский интерфейс правильно отображался с рабочим scrollView, но мне это кажется довольно хакерским. Я использую PreferenceKey для этого обходного пути. Я использую geometryReader внутри scrollview с высотой 0. Только для получения ширины моего VStack. В разделе preferenceKeyChange я обновляю переменную состояния и использую ее для своего элемента, чтобы установить его ширину и высоту.

import SwiftUI

struct Item: Identifiable {
    var id = UUID().uuidString
    var value: String
}

struct ContentView: View {
    @State var width: CGFloat = 0

    func items() -> [Item] {
        var items = [Item]()
        for i in 0..<100 {
            items.append(Item(value: "Item #\(i)"))
        }
        return items
    }

    struct WidthPreferenceKey: PreferenceKey {
        typealias Value = [CGFloat]

        static var defaultValue: [CGFloat] = [0]

        static func reduce(value: inout [CGFloat], nextValue: () -> [CGFloat]) {
            value.append(contentsOf: nextValue())
        }
    }

    var body: some View {
        ScrollView {
            VStack(spacing: 0) {
                Image(systemName: "circle.fill")
                    .resizable()
                    .scaledToFill()
                    .frame(width: 150, height: 150)
                    .padding()
                    .foregroundColor(.green)

                GeometryReader { geometry in
                    VStack {
                        EmptyView()

                    }.preference(key: WidthPreferenceKey.self, value: [geometry.size.width])
                }
                .frame(height: 0)

                ForEach(self.items()) { item in
                    Text(item.value)
                        .frame(width: self.width / CGFloat(2), height: self.width / CGFloat(2))
                        .background(Color.red)
                }
            }
            .padding()
        }
        .onPreferenceChange(WidthPreferenceKey.self) { value in
            self.width = value[0]
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Это единственный способ сделать это или есть более элегантный и простой способ сделать это?

1 Ответ

0 голосов
/ 05 мая 2020

Прежде всего, поскольку вы используете VStack, это означает, что вам нужна вертикальная прокрутка. Итак, вам нужно установить свойство maxHeight вместо maxWidth.

В вашем первом подходе вы оборачиваете свой for..l oop в GeometryReader перед тем, как обернуть его в VStack. Итак, SwiftUI не распознает, что вы хотите, чтобы эти элементы были в вертикальном стеке, до создания GeometryReader и его дочерних элементов. Итак, хороший обходной путь - поместить GeometryReader в качестве самого первого блока перед вашим scrollView, чтобы он работал:

import SwiftUI

struct Item: Identifiable {
    var id = UUID().uuidString
    var value: String
}

struct ContentView: View {

    func items() -> [Item] {
        var items = [Item]()
        for i in 0..<100 {
            items.append(Item(value: "Item #\(i)"))
        }
        return items
    }

    var body: some View {
        GeometryReader { geometry in
        ScrollView {
            VStack {

                Image(systemName: "circle.fill")
                    .resizable()
                    .scaledToFill()
                    .frame(width: 150, height: 150)
                    .padding()
                    .foregroundColor(.green)

                    ForEach(self.items()) { item in
                        Text(item.value)
                            .frame(width: geometry.size.width / CGFloat(2), height: geometry.size.width / CGFloat(2))
                            .background(Color.red)
                    }
            }
            .frame(maxHeight: .infinity)
            .padding()
        }
        }
        .background(Color.blue)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Проблема со вторым подходом заключается в том, что вы оборачиваете свой VStack бесконечной высоты в GeometryReader, который не обязательно имеет бесконечную высоту. Кроме того, GeometryReader - это блок SwiftUI для чтения размеров из внешнего блока, а не получения размеров из его дочерних элементов.

Вообще говоря, если вы хотите использовать геометрию текущего основного компонента SwiftUI, который вы создаете (или экран устройства, если это полноэкранный компонент), затем поместите GeometryReader на родительский уровень других компонентов. В противном случае вам нужно поместить компонент GeometryReader в качестве уникального дочернего элемента четко определенного блока SwiftUI размеров.

Отредактировано: чтобы добавить отступ, просто добавьте требуемый отступ в свой scrollView:

ScrollView{
  //...
}
.padding([.leading,.trailing],geometry.size.width / 8)

или только в одном направлении:

ScrollView{
  //...
}
.padding(leading,geometry.size.width / 8) // or any other value, e.g. : 30

Если вам нужно заполнение только для элементов для l oop, просто поместите для l oop в другой VStack и добавьте туда заполнение выше.

...