У меня есть экран 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:
Пример 2 - сетка 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)
}
}
}
}
}
}
}
Спасибо!