iOS16中的可滚动图表SwiftUI。

5
使用新的SwiftUI Charts框架,我们可以制作比可见屏幕更大的图表,并将其放置在ScrollView中使其可滚动。 就像这样:
var body : some View {
    
    GeometryReader { proxy in

        ScrollView(.horizontal, showsIndicators: false) {

            Chart {

                ForEach(data) { entry in

                    // ...
                }
            }
            .frame(width: proxy.size.width * 2)
        }
    }
}

有人知道是否可以通过编程的方式将滚动条移动到图表的某个特定区域吗?

我尝试使用ScrollViewReader,在x轴标签处设置ID,并试图使用scrollTo函数导航到其中任何一个位置,但没有成功:

Chart {

    /// ...
    
}
.chartXAxis {

    AxisMarks(values: .stride(by: .day)) { value in
    
        if let date : Date = value.as(Date.self) {
            Text(date, style: .date)
                .font(.footnote)
        }
    }
}
3个回答

6

这种简单的解决方法似乎可以解决问题。我使用了ZStack来放置图表,并在图表上叠加了HStack。HStack包含一堆符合Identifiable协议的不可见对象。这些不可见对象的数量、ID和位置与图表数据相匹配。

由于ZStack视图现在包含可识别的元素,因此ScrollViewReader按预期工作。

import SwiftUI
import Charts

struct ChartData: Identifiable {
    var day: Int
    var value: Int
    var id: String { "\(day)" }
}

struct ContentView: View {
    @State var chartData = [ChartData]()
    @State var scrollSpot = ""
    let items = 200
    let itemWidth: CGFloat = 30
    
    var body: some View {
        VStack {
            ScrollViewReader { scrollPosition in
                ScrollView(.horizontal) {
                    
                    // Create a ZStack with an HStack overlaying the chart.
                    // The HStack consists of invisible items that conform to the
                    // identifible protocol to provide positions for programmatic
                    // scrolling to the named location.
                    ZStack {
                        // Create an invisible rectangle for each x axis data point
                        // in the chart.
                        HStack(spacing: 0) {
                            ForEach(chartData) { item in
                                Rectangle()
                                    .fill(.clear)

                                    // Setting maxWidth to .infinity here, combined
                                    // with spacing:0 above, makes the rectangles
                                    // expand to fill the frame specified for the
                                    // chart below.
                                    .frame(maxWidth: .infinity, maxHeight: 0)

                                    // Here, set the rectangle's id to match the
                                    // charted data.
                                    .id(item.id)
                            }
                        }
                        
                        Chart(chartData) {
                            BarMark(x: .value("Day", $0.day),
                                    y: .value("Amount", $0.value),
                                    width: 20)
                        }
                        .frame(width: CGFloat(items) * itemWidth, height: 300)
                    }
                }
                .padding()
                .onChange(of: scrollSpot, perform: {x in
                    if (!x.isEmpty) {
                        scrollPosition.scrollTo(x)
                        scrollSpot = ""
                    }
                })
            }
            .onAppear(perform: populateChart)
        
            Button("Scroll") {
                if let x = chartData.last?.id {
                    print("Scrolling to item \(x)")
                    scrollSpot = x
                }
            }
            
            Spacer()
        }
    }

    func populateChart() {
        if !chartData.isEmpty { return }
        for i in 0..<items {
            chartData.append(ChartData(day: i, value: (i % 10) + 2))
        }
    }
}

我认为这应该可以直接使用 SwiftUI 实现。苹果公司的初始化器注释说它会 创建一个由可识别标记组成的图表。因此...如果标记是可识别的,那么期望 ScrollViewReader 与图表的标记一起工作就不是什么难以实现的事情。

但是却没有!

人们希望这只是苹果公司疏忽了,因为这个框架很新,他们将在即将发布的版本中暴露出图表标记的 id。


不错的解决方法。希望苹果在将来的版本中能够改进这个框架! - fcollf
2
使用Swift Charts的ScrollView的一个缺点是,如果您没有定位在图表的末尾,则垂直轴标签不可见。我通过隐藏垂直轴标签并将整个VStack嵌入另一个HStack中来解决了这个问题,然后在HStack中手动创建垂直轴标签。这不是理想的解决方案,因为标签的垂直间距是手动设置的,并且与图表无关。 - JJJSchmidt
1
另一种方法是使用ZStack,首先嵌入一个空的图表,显示在右侧的垂直轴上(没有水平轴),然后将ScrollView添加到同一个ZStack中,在其中包含具有实际数据的图表(此图表中没有垂直轴)。您可以使用相同的比例尺来保持两个图表同步。这并不完美,您必须使用GeometryReader来处理宽度和高度,但最终结果并不那么糟糕。 - fcollf
2
有趣的方法,我想不到。有一件事是肯定的,这个功能应该在框架中得到支持,所有这些琐碎的操作都是不必要的。 - JJJSchmidt

1

我的方法不是使用滚动视图,而是在用户在屏幕上拖动手指时过滤我的数据。

实际上,这是一个滑动窗口问题。您有一个窗口大小和一个偏移量。此外,您可以在图表视图本身上添加动画/转换,以复制滚动的感觉。

对我来说,动画并不完美,但是可以接受。根据您的数据和图表,可能适用于您或者效果更差。数据点的频率似乎是一个重要因素。我仍在尝试不同的方法,并在优化后更新此答案。

我喜欢这个解决方案,因为它没有像在Zstack中使用多个图表那样的hacky东西。所有轴始终可见。


0

如果有人在寻找答案,可以在这篇文章中找到。

关键在于ScrollView只包含一个项,即图表本身。所以你必须将它传递给scrollTo()

而且使用anchor参数,您可以指示去图表的尾端。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接