Заполните прямоугольник с динамическим размером RadialGradient, чтобы он идеально вписался в SwiftUI в Swift 5.1 для iOS.

У меня есть экран SwiftUI, который отображает данные в формате сетки, динамически изменяя размер ячеек, чтобы они всегда заполняли доступную ширину, и следя за тем, чтобы они всегда были квадратами.

Макет работает хорошо, но градиенты немного расстраивают. При создании RadialGradient необходимо указать beginRadius и endRadius в фактических точках экрана, а не в единицах (от 0,0 до 1,0).

Нравится:

RadialGradient(gradient: item.isBlue ? self.blue : self.red,
               center: .center,
               startRadius: 0.0,
               endRadius: 100.0)

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

Пример 1 - сетка 3x2:

Сетка 3x2

Пример 2 - сетка 2x3:

Сетка 2x3

Вот весь код примера проекта ContentView.swift, который ясно демонстрирует, что я имею в виду:

import SwiftUI

struct MyDataItem: Identifiable {
    let id: UUID = UUID()
    let text: String
    let isBlue: Bool
}

struct MyDataRow: Identifiable {
    let id: UUID = UUID()
    let items: [MyDataItem]
}

struct ContentView: View {

    @State private var datasetIndex: Int = 0
    @State private var sets = [
        // First data set
        [
            MyDataRow(items: [
                MyDataItem(text: "A", isBlue: false),
                MyDataItem(text: "B", isBlue: true),
                MyDataItem(text: "C", isBlue: true),
            ]),
            MyDataRow(items: [
                MyDataItem(text: "D", isBlue: false),
                MyDataItem(text: "E", isBlue: false),
                MyDataItem(text: "F", isBlue: false),
            ]),
        ],

        // Second data set
        [
            MyDataRow(items: [
                MyDataItem(text: "A", isBlue: false),
                MyDataItem(text: "B", isBlue: true),
            ]),
            MyDataRow(items: [
                MyDataItem(text: "C", isBlue: true),
                MyDataItem(text: "D", isBlue: false),
            ]),
            MyDataRow(items: [
                MyDataItem(text: "E", isBlue: false),
                MyDataItem(text: "F", isBlue: false),
            ]),
        ]
    ]

    var body: some View {
        VStack {
            Picker(selection: $datasetIndex, label: Text("Pick a data set")) {
                Text("Set 1").tag(0)
                Text("Set 2").tag(1)
            }.pickerStyle(SegmentedPickerStyle())
            GridThing(rows: self.$sets[self.datasetIndex])
            Spacer()
        }
    }
}

struct GridThing: View {
    @Binding var rows: [MyDataRow]

    private let spacing: CGFloat = 20.0

    let blue = Gradient(colors: [Color.blue, Color(red: 0.85, green: 0.85, blue: 1.0)])
    let red = Gradient(colors: [Color.red, Color(red: 1.0, green: 0.85, blue: 0.85)])

    var body: some View {
        VStack(alignment: .center, spacing: spacing) {
            ForEach(self.rows) { row in
                HStack(alignment: .top, spacing: self.spacing) {
                    ForEach(row.items) { item in
                        ZStack {
                            Rectangle()
                                .fill(RadialGradient(gradient: item.isBlue ? self.blue : self.red,
                                                     center: .center,
                                                     startRadius: 0.0,
                                                     endRadius: 100.0))
                                .aspectRatio(1.0, contentMode: .fit)
                            Text(item.text)
                                .foregroundColor(.black)
                        }
                    }
                }
            }
        }
    }
}

Я хочу, чтобы RadialGradient использовал endRadius, который соответствует высоте и ширине квадрата.

Я бы хотел, чтобы это делалось автоматически. LinearGradient, например, принимает UnitPoint для startPoint и endPoint, что было бы идеально, но для RadialGradient, я думаю, нам нужно сделать всю работу.

Моя основная мысль по этому поводу - каким-то образом включить GeometryReader.

Я попытался обернуть его вот так, вычислив endRadius с помощью GeometryReader. Это не удается, потому что "компилятор не может проверить тип этого выражения в разумные сроки; попробуйте разбить выражение на отдельные подвыражения":

var body: some View {
        VStack(alignment: .center, spacing: spacing) {
            GeometryReader { geom in
            ForEach(self.rows) { row in
                HStack(alignment: .top, spacing: self.spacing) {
                    ForEach(row.items) { item in
                        ZStack {
                            Rectangle()
                                .fill(RadialGradient(gradient: item.isBlue ? self.blue : self.red,
                                                     center: .center,
                                                     startRadius: 0.0,
                                                     endRadius: (geom.size.width - (self.spacing * (row.items.count + 1))) / row.items.count))
                                .aspectRatio(1.0, contentMode: .fit)
                            Text(item.text)
                                .foregroundColor(.black)
                        }
                    }
                }
            }
            }
        }
    }

Спасибо!


person drewster    schedule 14.12.2019    source источник
comment
Я думаю, что это ошибка, что RadialGradient не использует координаты UnitPoint для startRadius и endRadius, поэтому я подал об этом в FB7562363.   -  person marcprux    schedule 03.02.2020


Ответы (1)


Считыватель геометрии правильный, но вместо того, чтобы обернуть всю сетку, просто оберните квадрат, и тогда вам не нужно делать никаких дополнительных вычислений: ширина геометрии равна ширине квадрата, а это в два раза превышает радиус вашего градиента. вот детская площадка:

import SwiftUI
import PlaygroundSupport

struct V: View {
  var body: some View {
    VStack(alignment: .center, spacing: 10) {
      ForEach(0..<4) { row in
        HStack(alignment: .top, spacing: 10) {
          ForEach(0..<4) { item in
            ZStack {
              GeometryReader { geometry in
                Rectangle()
                  .fill(
                    RadialGradient(
                      gradient: Gradient(colors: [Color.white, Color.red, Color.green]),
                      center: .center,
                      startRadius: 0.0,
                      endRadius: geometry.size.width / 2 )
                   )
                  .aspectRatio(1.0, contentMode: .fit)
              }
              Text("text")
                .foregroundColor(.black)
            }
          }
        }
      }
    }
  }
}

PlaygroundPage.current.setLiveView(V())
person Josh Homann    schedule 14.12.2019
comment
Привет, Джош - в вашем примере это выглядит хорошо, но не работает, если вы делаете больше строк, чем помещается на экране. Например, сделайте свой первый ForEach ForEach (0 .. ‹15). Размер прямоугольников в этом случае нарушается (они становятся крошечными, видимо, в попытке уместить их все по вертикали). Я попытался обернуть все это прокруткой, но это не помогло. И я попытался прикрепить .frame (maxSize: .infinity) к VStack. - person drewster; 15.12.2019