SwiftUI - поместите прямоугольник относительно другого представления в ZStack - PullRequest
0 голосов
/ 20 марта 2020

Задача

Я создаю гистограмму с помощью SwiftUI, и я ищу лучший подход для позиционирования среднего индикатора, горизонтальной желтой линии, показанной на рисунке ниже:

enter image description here

Цель

Мне нужно иметь возможность расположить желтую линию (прямоугольник с 1 точкой по высоте) относительно столбцов (также прямоугольников) графика. Для простоты давайте представим, что самый высокий возможный столб - 100 пунктов, а средний - 75. Моя цель - нарисовать желтую линию ровно на 75 пунктов выше дна столбцов.

Проблема

В моем примере я размещаю линию сверху графика с использованием ZStack. Я могу расположить линию вертикально, используя GeometryReader, однако я не знаю, как расположить ее относительно нижней части столбцов.

Вот мой код игровой площадки:

import SwiftUI
import PlaygroundSupport

struct BarChart: View {
    var measurements : [Int]
    var pastColor: Color
    var todayColor: Color
    var futureColor: Color

    let labelsColor = Color(UIColor(white: 0.5, alpha: 1.0))

    func monthAbbreviationFromInt(_ day: Int) -> String {
        let da = Calendar.current.shortWeekdaySymbols
        return da[day]
    }

    var body: some View {
        ZStack {

            VStack {

                HStack {
                    Rectangle()
                        .fill(Color.yellow)
                        .frame(width: 12, height: 2, alignment: .center)

                    Text("Desired level")
                        .font(.custom("Helvetica", size: 10.0))
                        .foregroundColor(labelsColor)
                }
                .padding(.bottom , 50.0)

                HStack {

                    ForEach(0..<7) { day in

                        VStack {

                            Spacer()

                            Text(String(self.measurements[day]))
                                .frame(width: CGFloat(40), height: CGFloat(20))
                                .font(.footnote)
                                .lineLimit(1)
                                .minimumScaleFactor(0.5)
                                .foregroundColor(self.labelsColor)

                            Rectangle()
                                .fill(day > 0 ? self.futureColor : self.pastColor)
                                .frame(width: 20, height: CGFloat(self.measurements[day]*2))
                                .transition(.slide)
                                .cornerRadius(3.0)

                            Text("\(self.monthAbbreviationFromInt(day))\n\(day)")
                                .multilineTextAlignment(.center)
                                .font(.footnote)
                                .frame(height: 20)
                                .lineLimit(2)
                                .minimumScaleFactor(0.2)
                                .foregroundColor(self.labelsColor)

                        }
                        .frame(height: 200, alignment: .bottom )
                    }

                }
            }
            .padding()

            VStack {
                GeometryReader { geometry in
                    //Yellow line
                    Rectangle()
                        .path(in: CGRect(x: 0,
                                     y: 350, //This is now a random value, but I should detect and use the bottom coordinate of the bars to place the line at exactly 75 points above that coordinate
                        width: geometry.size.width,
                        height: 1))
                        .fill(Color.yellow)

                }
            }
        }
    }
}

Как узнать относительное положение нижней части стержней, чтобы я мог правильно расположить линию?

Есть ли другой подход вы бы предложили?

Заранее спасибо

1 Ответ

1 голос
/ 20 марта 2020

пример

import SwiftUI

struct Data: Identifiable {
    let id = UUID()
    let value: CGFloat
    let color: Color
}
struct ContentView: View {
    let data = [Data(value: 35, color: .red),
                  Data(value: 100, color: .green),
                  Data(value: 70, color: .yellow),
                  Data(value: 25, color: .blue),
                  Data(value: 55, color: .orange)]

    var maxValue: CGFloat {
        data.map { (element) -> CGFloat in
            element.value
        }.max() ?? 1.0
    }
    var average: CGFloat {
        guard !data.isEmpty else { return 0 }
        let sum = data.reduce(into: CGFloat.zero) { (res, data) in
            res += data.value
        }
        return sum / CGFloat(data.count)
    }
    var body: some View {

        HStack {
            ForEach(data) { (bar) in
                Bar(t: bar.value / self.maxValue, color: bar.color, width: 20)
            }
        }
        .background(Color.pink.opacity(0.1))
        .overlay(
            GeometryReader { proxy in
                Color.primary.frame(height: 1).position(x: proxy.size.width / 2, y: proxy.size.height * ( 1 - self.average / self.maxValue))
            }
        ).padding(.vertical, 100).padding(.horizontal, 20)

    }
}

struct Bar: View {
    let t: CGFloat
    let color: Color
    let width: CGFloat

    var body: some View {
        GeometryReader { proxy in
            Path { (path) in
                path.move(to: .init(x: proxy.size.width / 2, y: proxy.size.height))
                path.addLine(to: .init(x: proxy.size.width / 2, y: 0))
                }
            .trim(from: 0, to: self.t)
            .stroke(lineWidth: self.width)
            .foregroundColor(self.color)
        }
    }
}

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

и результат enter image description here

или немного приняты для отображения меток

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...