Как разбить несколько операторов ForEach в GeometryReader и избежать "Компилятор не может проверить тип .. в разумные сроки"

Я пытался подражать руководству Рэя Вендерлиха по графическому интерфейсу Swift для моей собственной проблемы, и в основном он работает хорошо, но я постоянно нажимаю на ошибку "Компилятор не может проверить это выражение в разумное время" из-за сложность просмотра.

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

protocol GraphView {
    var xAxisMaxRange: CGFloat { get }
    var xAxisMinRange: CGFloat { get }
    var xAxisOffset: CGFloat { get }
    var yAxisMaxRange: CGFloat { get }
    var yAxisMinRange: CGFloat { get }
    var yAxisOffset: CGFloat { get }
    var numVerticalGridLines: Int { get }
    var numVerticalGridLinesPlusOne: Int { get }
    var numHorizontalGridLines: Int { get }
    var numHorizontalGridLinesPlusOne: Int { get }
}

extension GraphView {
    func xAxisTransform(_ value: CGFloat, plotScreenWidth: CGFloat) -> CGFloat {
        return (value / (xAxisMaxRange - xAxisMinRange)) * plotScreenWidth
    }

    func yAxisTransform(_ value: CGFloat, plotScreenHeight: CGFloat) -> CGFloat {
        return plotScreenHeight - (value / (yAxisMaxRange - yAxisMinRange)) * plotScreenHeight
    }

    func plotAreaWidth(_ readerWidth: CGFloat) -> CGFloat {
        return readerWidth - 2 * yAxisOffset
    }

    func plotAreaHeight(_ readerHeight: CGFloat) -> CGFloat {
        return readerHeight - 2 * xAxisOffset
    }

    func plotAreaSize(_ readerSize: CGSize) -> CGSize {
        return CGSize(width: plotAreaWidth(readerSize.width), height: plotAreaHeight(readerSize.height))
    }

    func point(x: Double, y: Double, readerSize: CGSize) -> CGPoint {
        return CGPoint(x: yAxisOffset + xAxisTransform(CGFloat(x), plotScreenWidth: plotAreaSize(readerSize).width),
                       y: xAxisOffset + yAxisTransform(CGFloat(y), plotScreenHeight: plotAreaSize(readerSize).height))
    }

    func point(x: Double, y: CGFloat, readerSize: CGSize) -> CGPoint {
        return point(x: x, y: Double(y), readerSize: readerSize)
    }

    func point (x: CGFloat, y: Double, readerSize: CGSize) -> CGPoint {
        return point(x: Double(x), y: y, readerSize: readerSize)
    }

    func point(x: CGFloat, y: CGFloat, readerSize: CGSize) -> CGPoint {
        return point(x: Double(x), y: Double(y), readerSize: readerSize)
    }

    func xAxisLabel(labelIndex: Int) -> String {
        let labelValueSeconds = CGFloat(labelIndex) * (xAxisMaxRange - xAxisMinRange) / CGFloat(numVerticalGridLines)
        let labelValueHours = Int(labelValueSeconds / 3600.0)
        var labelValueHour = labelValueHours % 12
        if labelValueHour == 0 { labelValueHour = 12}
        return String(format: "%d", labelValueHour)+(labelValueHours<12 || labelValueHours == 24 ? " AM" : " PM")
    }

    func yAxisLabel(labelIndex: Int) -> String {
        let labelValue = CGFloat(labelIndex) * (yAxisMaxRange - xAxisMinRange) / CGFloat(numHorizontalGridLines)
        return String(format: "%.1f", labelValue)+(labelIndex == numHorizontalGridLines ? " ms" : "")
    }

    func xAxisLabelOffset(labelIndex: Int, readerSize: CGSize) -> CGFloat {
        let offsetBetweenGrids = plotAreaWidth(readerSize.width) / CGFloat(numVerticalGridLines)
        return yAxisOffset + CGFloat(labelIndex) * offsetBetweenGrids
    }

    func yAxisLabelOffset(labelIndex: Int, readerSize: CGSize) -> CGFloat {
        let offsetBetweenGrids = plotAreaHeight(readerSize.height) / CGFloat(numHorizontalGridLines)
        return xAxisOffset + plotAreaHeight(readerSize.height) - CGFloat(labelIndex) * offsetBetweenGrids - offsetBetweenGrids / 2.0
    }

    func xAxisGridWidth(readerSize: CGSize) -> CGFloat {
        return plotAreaWidth(readerSize.width) / CGFloat(numVerticalGridLines)
    }

    func yAxisGridHeight(readerSize: CGSize) -> CGFloat {
        return plotAreaHeight(readerSize.height) / CGFloat(numHorizontalGridLines)
    }
}

struct DailyGraph: View, GraphView {

    let xAxisMaxRange: CGFloat = 24 * 3600
    let xAxisMinRange: CGFloat = 0
    let xAxisOffset: CGFloat = 50
    let yAxisMaxRange: CGFloat = 60 // ms
    let yAxisMinRange: CGFloat = 0
    let yAxisOffset: CGFloat = 60
    let numVerticalGridLines: Int = 24
    let numVerticalGridLinesPlusOne: Int
    let numHorizontalGridLines: Int = 6
    let numHorizontalGridLinesPlusOne: Int

    @ObservedObject var historicalDataManager: HistoricalDataManager

    init(historicalDataManager: HistoricalDataManager) {
        self.historicalDataManager = historicalDataManager
        numVerticalGridLinesPlusOne = numVerticalGridLines + 1
        numHorizontalGridLinesPlusOne = numHorizontalGridLines + 1
    }

    var body: some View {
        GeometryReader { reader in
            Text("Response (ms) vs Time of Day")
                .frame(width: reader.size.width, height: self.xAxisOffset, alignment: .center)
                .font(.largeTitle)

            // draw the vertical grid lines
            ForEach(0..<self.numVerticalGridLinesPlusOne) { line in
                Group {
                    Path { path in
                        let gridBottom = self.point(x: CGFloat(line) * (self.xAxisMaxRange - self.xAxisMinRange) / CGFloat(self.numVerticalGridLines),
                                                    y: self.yAxisMinRange,
                                                    readerSize: reader.size)
                        let gridTop = self.point(x: CGFloat(line) * (self.xAxisMaxRange - self.xAxisMinRange) / CGFloat(self.numVerticalGridLines),
                                                    y: self.yAxisMaxRange,
                                                    readerSize: reader.size)

                        path.move(to: gridBottom)
                        path.addLine(to: gridTop)
                    }.stroke(line == 0 || line == self.numVerticalGridLines ? Color.black : Color.gray)
                    Text(self.xAxisLabel(labelIndex: line))
                        .frame(width: self.xAxisGridWidth(readerSize: reader.size), height: self.xAxisOffset, alignment: .center)
                        .offset(x: self.xAxisLabelOffset(labelIndex: line, readerSize: reader.size) - self.xAxisGridWidth(readerSize: reader.size) / 2.0,
                                y: reader.size.height - self.xAxisOffset)

                }
            }
            // draw the horizontal grid lines
            ForEach(0..<self.numHorizontalGridLinesPlusOne) { line in
                Group {
                    Path { path in
                        let gridLeft = self.point(x: self.xAxisMinRange,
                                                  y: CGFloat(line) * (self.yAxisMaxRange - self.yAxisMinRange) / CGFloat(self.numHorizontalGridLines),
                                                  readerSize: reader.size)
                        let gridRight = self.point(x: self.xAxisMaxRange,
                                                   y: CGFloat(line) * (self.yAxisMaxRange - self.yAxisMinRange) / CGFloat(self.numHorizontalGridLines),
                                                   readerSize: reader.size)
                        path.move(to: gridLeft)
                        path.addLine(to: gridRight)
                    }.stroke(line == 0 || line == self.numHorizontalGridLines ? Color.black : Color.gray)
                    Text(self.yAxisLabel(labelIndex: line))
                        .frame(width: self.yAxisOffset, height: self.yAxisGridHeight(readerSize: reader.size), alignment: .center)
                        .offset(x: 0, y: self.yAxisLabelOffset(labelIndex: line, readerSize: reader.size))
                }
            }
            // draw the graph signal
            ForEach(0..<$historicalDataManager.todaysData.count) { dataIndex in
                Group {
                    Text("\(dataIndex)")
                }
            }
        }
    }
}

class HistoricalDataManager : ObservableObject {
    // minimal class only for compilation..
    @Published var todaysData: [(Date,Double)] = []
}

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

1 ответ

Вот демонстрация возможного способа (например, первая нарушающая итерация, следующая возможная - разделение генерации путей и т. Д.)

private func headerView(in reader: GeometryProxy) -> some View {
    Text("Response (ms) vs Time of Day")
        .frame(width: reader.size.width, height: self.xAxisOffset, alignment: .center)
        .font(.largeTitle)
}

private func footerView(in reader: GeometryProxy) -> some View {
    ForEach(0..<self.historicalDataManager.todaysData.count) { dataIndex in
        Group {
            Text("\(dataIndex)")
        }
    }
}

private func verticalGrid(for line: Int, in reader: GeometryProxy) -> some View {
    Group {
        Path { path in
            let gridBottom = self.point(x: CGFloat(line) * (self.xAxisMaxRange - self.xAxisMinRange) / CGFloat(self.numVerticalGridLines),
                                        y: self.yAxisMinRange,
                                        readerSize: reader.size)
            let gridTop = self.point(x: CGFloat(line) * (self.xAxisMaxRange - self.xAxisMinRange) / CGFloat(self.numVerticalGridLines),
                                        y: self.yAxisMaxRange,
                                        readerSize: reader.size)

            path.move(to: gridBottom)
            path.addLine(to: gridTop)
        }.stroke(line == 0 || line == self.numVerticalGridLines ? Color.black : Color.gray)
        Text(self.xAxisLabel(labelIndex: line))
            .frame(width: self.xAxisGridWidth(readerSize: reader.size), height: self.xAxisOffset, alignment: .center)
            .offset(x: self.xAxisLabelOffset(labelIndex: line, readerSize: reader.size) - self.xAxisGridWidth(readerSize: reader.size) / 2.0,
                    y: reader.size.height - self.xAxisOffset)

    }
}

private func horizontalGrid(for line: Int, in reader: GeometryProxy) -> some View {
    Group {
        Path { path in
            let gridLeft = self.point(x: self.xAxisMinRange,
                                      y: CGFloat(line) * (self.yAxisMaxRange - self.yAxisMinRange) / CGFloat(self.numHorizontalGridLines),
                                      readerSize: reader.size)
            let gridRight = self.point(x: self.xAxisMaxRange,
                                       y: CGFloat(line) * (self.yAxisMaxRange - self.yAxisMinRange) / CGFloat(self.numHorizontalGridLines),
                                       readerSize: reader.size)
            path.move(to: gridLeft)
            path.addLine(to: gridRight)
        }.stroke(line == 0 || line == self.numHorizontalGridLines ? Color.black : Color.gray)
        Text(self.yAxisLabel(labelIndex: line))
            .frame(width: self.yAxisOffset, height: self.yAxisGridHeight(readerSize: reader.size), alignment: .center)
            .offset(x: 0, y: self.yAxisLabelOffset(labelIndex: line, readerSize: reader.size))
    }
}

// ... and now - elegant body
var body: some View {
    GeometryReader { reader in
        self.headerView(in: reader)

        // draw the vertical grid lines
        ForEach(0..<self.numVerticalGridLinesPlusOne) { line in
            self.verticalGrid(for: line, in: reader)
        }
        // draw the horizontal grid lines
        ForEach(0..<self.numHorizontalGridLinesPlusOne) { line in
            self.horizontalGrid(for: line, in: reader)
        }

        // draw the graph signal
        self.footerView(in: reader)
    }
}
Другие вопросы по тегам