SwiftUI Charts标注适配问题:点击边缘柱状图时图表偏移
SwiftUI Charts 柱状图点击边缘标注导致偏移的解决方案
问题原因
点击柱状图边缘区域时,通过RuleMark的.annotation添加的标注会因容器适配需求触发Chart的布局重计算,导致柱状图整体偏移。本质是annotation属于Chart内部元素,其尺寸变化会影响Chart的布局空间分配。
解决方案
将标注移至chartOverlay中实现,让标注作为Chart的上层视图独立渲染,避免干扰Chart本身的布局。同时添加边缘位置修正,确保标注始终在容器内显示。
修改后的完整代码
struct BarChartView: View { struct BarData: Identifiable, Equatable { var type: String var date: Date var count: Float var id: Int } var data: [BarData] = [ BarData(type: "test1", date: Date(), count: 10.0, id: 1), BarData(type: "test2", date: Date().addingTimeInterval(86400), count: 25.0, id: 2), BarData(type: "test3", date: Date().addingTimeInterval(172800), count: 40.0, id: 3) ] @State var activeItem: BarData? private let annotationWidth: CGFloat = 150 // 标注预估宽度,可根据实际内容调整 var body: some View { NavigationStack{ VStack(alignment : .leading, spacing: 15) { Text("testing errors") VStack{ Chart(data) { item in BarMark( x: .value("Date", item.date, unit: .day), y: .value("Sum", item.count ) ) // 保留虚线标记,移除原annotation if let activeItem, activeItem.id == item.id { RuleMark(x: .value("Day", activeItem.date)) .lineStyle(.init(lineWidth: 2, miterLimit: 2, dash: [2], dashPhase: 5)) } } .chartOverlay { proxy in GeometryReader { geometry in Rectangle() .fill(.clear) .contentShape(Rectangle()) .gesture( DragGesture() .onChanged { value in let location = value.location guard let date = proxy.value(atX: location.x) as Date? else { activeItem = nil return } // 匹配最近的日期数据 activeItem = data.min(by: { abs($0.date.timeIntervalSince(date)) < abs($1.date.timeIntervalSince(date)) }) } ) // 在Overlay中渲染标注 if let activeItem { let xPos = proxy.position(forX: activeItem.date) ?? 0 let yPos = proxy.position(forY: activeItem.count) ?? 0 VStack(alignment: .leading, spacing: 6){ HStack{ Text("Type") .font(.caption) .foregroundColor(.gray) Text(activeItem.type) .font(.title3.bold()) } HStack{ Text("Sum") .font(.caption) .foregroundColor(.gray) Text("\(activeItem.count, specifier: "%.2f")") .font(.title3.bold()) } } .padding(.horizontal, 10) .padding(.vertical, 4) .background( RoundedRectangle(cornerRadius: 6, style: .continuous) .fill(.black.shadow(.drop(radius: 2))) ) .foregroundColor(.white) // 添加白色文字,提升可读性 .position(x: xPos, y: yPos - 20) // 修正边缘偏移,避免标注超出容器 .offset(x: calculateAnnotationOffset(xPos: xPos, containerWidth: geometry.size.width)) } } } .chartYScale(domain: 0...50) .frame(height: 250) } .padding() .background{ RoundedRectangle(cornerRadius: 10, style: .continuous) .stroke(.gray, lineWidth: 1) } } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) .padding(.top, 10) .navigationTitle("Test charts") } } // 计算标注的水平偏移量,确保不超出容器 private func calculateAnnotationOffset(xPos: CGFloat, containerWidth: CGFloat) -> CGFloat { let halfWidth = annotationWidth / 2 if xPos < halfWidth { return halfWidth - xPos } else if xPos > containerWidth - halfWidth { return containerWidth - halfWidth - xPos } return 0 } }
关键修改点
- 移除RuleMark的annotation:将标注移至
chartOverlay,使其成为独立于Chart布局的上层视图 - 添加边缘偏移修正:通过
calculateAnnotationOffset方法,确保标注在靠近容器边缘时自动调整位置,避免超出可视区域 - 补全手势逻辑:完善
chartOverlay中的拖拽手势,实现点击/拖拽时的选中逻辑 - 优化标注样式:添加白色文字提升深色背景下的可读性
内容的提问来源于stack exchange,提问作者Okeyman
相关产品推荐
相关产品推荐

