0

我一直在尝试针对我自己的问题模拟 Ray Wenderlich Swift UI 图形教程,并且效果很好,但是由于视图的复杂性。

我还尝试将各个部分分解为它们自己的视图,但是我无法引用我的坐标转换辅助方法,这些方法将工程单位转换为子视图中驻留在父视图中的窗口单位。这是我从教程中改编的代码:

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 个视图的限制,但将视图重构为更小的组并没有帮助。一个例子将不胜感激。

4

1 回答 1

0

这是可能方式的演示(例如,第一次中断迭代,下一个可能的 - 分离路径生成,等等)

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)
    }
}
于 2020-05-10T05:42:04.117 回答