美文网首页
在 Swift图表中使用Foundation库中的测量类型

在 Swift图表中使用Foundation库中的测量类型

作者: 韦弦Zhy | 来源:发表于2022-10-26 17:51 被阅读0次

    在 Swift 图表中使用Foundation 库中的测量类型

    在这篇文章中,我们将建立一个条形图,比较基督城地区自然散步的持续时间。我们将使用今年推出的新的Swift Charts框架,并将看到如何绘制默认不符合Plottable协议的类型的数据,如Measurement<UnitDuration>

    定义图表的数据

    让我们先定义一下要在图表中展现的数据。

    我们声明了一个包含标题和步行时间(小时)的Walk结构体。我们使用 Foundation 框架中的测量类型Measurement和单位类型UnitDuration来表示每次步行的时间。

    struct Walk {
        let title: String
        let duration: Measurement<UnitDuration>
    }
    

    我们在数组works中存储要在图表中显示的数据。

    let walks = [
        Walk(
            title: "Taylors Mistake to Sumner Beach Coastal Walk",
            duration: Measurement(value: 3.1, unit: .hours)
        ),
        Walk(
            title: "Bottle Lake Forest",
            duration: Measurement(value: 2, unit: .hours)
        ),
        Walk(
            title: "Old Halswell Quarry Loop",
            duration: Measurement(value: 0.5, unit: .hours)
        ),
        ...
    ]
    

    尝试直接在图表中使用测量值

    让我们定义一个Chart,并将walks数组作为数据参数传递给它。因为我们知道我们的walk标题是唯一的,所以我们可以直接使用它们作为id,但你也可以将你的数据模型改为Identifiable

    Chart(walks, id: \.title) { walk in
        BarMark(
            x: .value("Duration", walk.duration),
            y: .value("Walk", walk.title)
        )
    }
    

    注意,因为Measurement<UnitDuration>没有遵守Plottable协议,我们会得到一个错误:「Initializer 'init(x:y:width:height:stacking:)' requires that 'Measurement<UnitDuration>' conform to 'Plottable'」

    BarkMark的初始化器期望收到一个用于x和y的PlottableValue参数。而且PlottableValue的值类型必须符合Plottable协议。

    我们有几个选择来解决这个错误。我们可以提取测量值的value,它是一个Double类型,它是默认符合Plottable的,我们可以扩展具有Plottable一致性的Measurement<UnitDuration>,或者我们可以定义一个包装了测量的类型并使其符合Plottable协议。

    如果我们简单地从测量值中提取,我们就会失去上下文,不知道用什么单位来创建测量值。这意味着,我们将无法正确格式化图表的标签来向用户表示单位。虽然我们可以记住我们在创建测量时使用了小时hours,但这并不理想。例如,我们可以决定以后改变数据模型,以分钟为单位存储持续时间,或者数据可能来自其他地方,所以手动重构单位并不是一个完美的解决方案。

    Plottable的一致性来扩展Measurement<UnitDuration>是可行的,但根据Swift中关于外部类型的追溯一致性的警告(Warning for Retroactive Conformances of External Types),如果Swift Charts在未来添加了这种一致性,它可能会被破坏。

    我们将研究如何定义我们自己的类型来包装 measurement,并为我们的自定义类型添加Plottable的一致性。

    设计一个符合 Plottable 标准的包装器类型

    我们将定义一个自定义的PlottableMeasurement类型,并使其成为通用的,所以它可以容纳任何类型的单位的测量类型。

    struct PlottableMeasurement<UnitType: Unit> {
        var measurement: Measurement<UnitType>
    }
    

    然后,我们将为PlottableMeasurement添加Plottable的一致性,其单位为UnitDuration类型。我们可以在将来添加对其他单位的支持。

    extension PlottableMeasurement: Plottable where UnitType == UnitDuration {
        var primitivePlottable: Double {
            self.measurement.converted(to: .minutes).value
        }
        
        init?(primitivePlottable: Double) {
            self.init(
                measurement: Measurement(
                    value: primitivePlottable,
                    unit: .minutes
                )
            )
        }
    }
    

    Plottable协议有两个要求:primitivePlottable属性必须返回原始类型之一,如DoubleStringDate,以及一个可失败的初始化器,从原始plottable类型创建一个值。

    我决定将测量值转换为分钟,但你可以选择适合你需要的任何其他单位。只是在与原始值转换时要使用相同的单位,这一点很重要。

    我们现在可以更新我们的图表,以使用我们的自定义Plottable类型。

    Chart(walks, id: \.title) { walk in
        BarMark(
            x: .value(
                "Duration",
                PlottableMeasurement(measurement: walk.duration)
            ),
            y: .value("Walk", walk.title)
        )
    }
    

    它可以工作,但X轴上的标签没有格式化,没有向用户显示测量单位。我们接下来要解决这个问题。

    步行时间柱状图的截图,X轴上的标签显示为分钟数,但没有单位

    显示带有测量单位的格式化标签

    为了定制X轴上的标签,我们将使用chartXAxis(content:)修改器,并用传递给我们的值重构x轴的标记。

    Chart(walks, id: \.title) { ... }
        .chartXAxis {
            AxisMarks { value in
                AxisGridLine()
                AxisValueLabel("""
                \(value.as(PlottableMeasurement.self)!
                    .measurement
                    .converted(to: .hours),
                format: .measurement(
                    width: .narrow,
                    numberFormatStyle: .number.precision(
                        .fractionLength(0))
                    )
                )
                """)
            }
        }
    

    我们首先添加网格线,然后重构给定值的标签。

    AxisValueLabel在初始化器中接受一个LocalizedStringKey,它可以通过插值测量和指定其格式风格来构建。

    我们收到的值是使用我们在Plottable一致性中定义的初始化器创建的,所以在我们的案例中,测量值是以分钟为单位提供的。但我相信对于这个特定的图表,使用小时会更好。我们可以很容易地将测量值转换为插值内部所需的单位。在这里,我们确定该值是PlottableMeasurement类型的,所以我们可以强制解包类型转换。

    我选择了缩小的格式和小数点后零位数作为数字样式,但你可以根据你的具体图表调整这些设置。

    最后的结果是在X轴上显示以小时为单位的格式化持续时间。


    步行时间柱状图的截图,X轴上的标签显示了以小时为单位的格式化数字

    你可以从我们的GitHub repo中获得这篇文章中使用的项目的完整示例代码

    Using Measurements from Foundation for values in Swift Charts
    https://nilcoalescing.com/blog/UsingMeasurementsFromFoundationAsValuesInSwiftCharts/

    相关文章

      网友评论

          本文标题:在 Swift图表中使用Foundation库中的测量类型

          本文链接:https://www.haomeiwen.com/subject/ppqgzrtx.html