我一直在尝试针对我自己的问题模拟 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 个视图的限制,但将视图重构为更小的组并没有帮助。一个例子将不胜感激。