admin管理员组文章数量:1291800
First of all, I am quite new to SwiftUI. I have a problem with the annotation of the BarMarks. The annotation is covered by the following BarMarks. Is there a way to prevent this?
import Foundation
import SwiftUI
import Charts
struct WaterConsumptionChart: View {
@ObservedObject var model: ConsumptionDataModel
@State private var selectedItem: ConsumptionData?
@State private var showTooltip: Bool = false
private var gradientColors = [Color.primarySwiftUI.opacity(0.8),
Color.groheLink.opacity(0.8)]
var body: some View {
VStack {
Chart(model.data) { item in
BarMark(
x: .value("", item.text),
y: .value("consumption", item.consumption ?? 0),
width: .automatic
)
.foregroundStyle(LinearGradient(
gradient: Gradient(colors: gradientColors),
startPoint: .top,
endPoint: .bottom
))
.cornerRadius(4)
.annotation(position: .top, alignment: .center) {
let show = ((selectedItem?.text ?? "") == item.text) && ((item.consumption ?? 0) > 0)
TooltipView(value: "\(selectedItem?.text ?? "") • \(selectedItem?.consumption ?? 0)l")
.opacity(show ? 1 : 0)
.offset(y: -2)
}
if let average = model.average, average > 0 {
RuleMark(y: .value("", average))
.foregroundStyle(.primarySwiftUI)
.lineStyle(.init(lineWidth: 1))
} else {
RuleMark(y: .value("", 0.0))
.foregroundStyle(.primarySwiftUI)
.lineStyle(.init(lineWidth: 0))
}
}
.padding([.leading, .trailing], 16)
.chartXAxis {
AxisMarks(values: .automatic(minimumStride: 3, desiredCount: 7)) { value in
let period = viewModel.period?.period
let modulo = period == .day ? 4 : 5
let entry = Int(value.as(String.self) ?? "") ?? 0
if value.count <= 12 || entry % modulo == 0 || value.index == 0 {
let uiFont = Fonts.Inter.regular.of(size: 10)
AxisValueLabel()
.foregroundStyle(.primarySwiftUI)
.font(Font(uiFont))
.offset(x: 0, y: 10)
AxisGridLine()
.foregroundStyle(.greyE1E3E7)
.offset(x: 0, y: 0)
}
}
}
.chartYAxis {
AxisMarks(values: .automatic(desiredCount: 7)) { value in
if let intValue = value.as(Int.self) {
if intValue == 0 {
AxisGridLine()
.foregroundStyle(.primarySwiftUI)
} else {
let uiFont = Fonts.Inter.regular.of(size: 10)
AxisValueLabel("\(intValue)l")
.foregroundStyle(.primarySwiftUI)
.font(Font(uiFont))
AxisGridLine()
.foregroundStyle(.greyE1E3E7)
}
}
}
}
.animation(.smooth, value: viewModel.data)
.chartOverlay { pr in
GeometryReader { geoProxy in
Rectangle()
.fill(.clear).contentShape(Rectangle()).padding([.leading, .trailing], 16)
.onTapGesture(perform: { value in
let origin = geoProxy[pr.plotAreaFrame].origin
let location = CGPoint(x: value.x - origin.x, y: value.y - origin.y)
if let selected = pr.value(atX: location.x, as: String.self),
let dataItem = viewModel.data.first(where: { $0.text == selected }) {
if dataItem.consumption ?? 0 > 0 { UIImpactFeedbackGenerator(style: .light).impactOccurred() }
self.selectedItem = dataItem
} else {
self.selectedItem = nil
}
})
}
}
}
}
}
I tried to experiment with .zIndex(value: Double) but unfortunately I could not solve the problem. I also need compatibility with iOS 16 and .zIndex is only available from iOS 17. Does anyone have an idea?
First of all, I am quite new to SwiftUI. I have a problem with the annotation of the BarMarks. The annotation is covered by the following BarMarks. Is there a way to prevent this?
import Foundation
import SwiftUI
import Charts
struct WaterConsumptionChart: View {
@ObservedObject var model: ConsumptionDataModel
@State private var selectedItem: ConsumptionData?
@State private var showTooltip: Bool = false
private var gradientColors = [Color.primarySwiftUI.opacity(0.8),
Color.groheLink.opacity(0.8)]
var body: some View {
VStack {
Chart(model.data) { item in
BarMark(
x: .value("", item.text),
y: .value("consumption", item.consumption ?? 0),
width: .automatic
)
.foregroundStyle(LinearGradient(
gradient: Gradient(colors: gradientColors),
startPoint: .top,
endPoint: .bottom
))
.cornerRadius(4)
.annotation(position: .top, alignment: .center) {
let show = ((selectedItem?.text ?? "") == item.text) && ((item.consumption ?? 0) > 0)
TooltipView(value: "\(selectedItem?.text ?? "") • \(selectedItem?.consumption ?? 0)l")
.opacity(show ? 1 : 0)
.offset(y: -2)
}
if let average = model.average, average > 0 {
RuleMark(y: .value("", average))
.foregroundStyle(.primarySwiftUI)
.lineStyle(.init(lineWidth: 1))
} else {
RuleMark(y: .value("", 0.0))
.foregroundStyle(.primarySwiftUI)
.lineStyle(.init(lineWidth: 0))
}
}
.padding([.leading, .trailing], 16)
.chartXAxis {
AxisMarks(values: .automatic(minimumStride: 3, desiredCount: 7)) { value in
let period = viewModel.period?.period
let modulo = period == .day ? 4 : 5
let entry = Int(value.as(String.self) ?? "") ?? 0
if value.count <= 12 || entry % modulo == 0 || value.index == 0 {
let uiFont = Fonts.Inter.regular.of(size: 10)
AxisValueLabel()
.foregroundStyle(.primarySwiftUI)
.font(Font(uiFont))
.offset(x: 0, y: 10)
AxisGridLine()
.foregroundStyle(.greyE1E3E7)
.offset(x: 0, y: 0)
}
}
}
.chartYAxis {
AxisMarks(values: .automatic(desiredCount: 7)) { value in
if let intValue = value.as(Int.self) {
if intValue == 0 {
AxisGridLine()
.foregroundStyle(.primarySwiftUI)
} else {
let uiFont = Fonts.Inter.regular.of(size: 10)
AxisValueLabel("\(intValue)l")
.foregroundStyle(.primarySwiftUI)
.font(Font(uiFont))
AxisGridLine()
.foregroundStyle(.greyE1E3E7)
}
}
}
}
.animation(.smooth, value: viewModel.data)
.chartOverlay { pr in
GeometryReader { geoProxy in
Rectangle()
.fill(.clear).contentShape(Rectangle()).padding([.leading, .trailing], 16)
.onTapGesture(perform: { value in
let origin = geoProxy[pr.plotAreaFrame].origin
let location = CGPoint(x: value.x - origin.x, y: value.y - origin.y)
if let selected = pr.value(atX: location.x, as: String.self),
let dataItem = viewModel.data.first(where: { $0.text == selected }) {
if dataItem.consumption ?? 0 > 0 { UIImpactFeedbackGenerator(style: .light).impactOccurred() }
self.selectedItem = dataItem
} else {
self.selectedItem = nil
}
})
}
}
}
}
}
I tried to experiment with .zIndex(value: Double) but unfortunately I could not solve the problem. I also need compatibility with iOS 16 and .zIndex is only available from iOS 17. Does anyone have an idea?
Share Improve this question edited Feb 13 at 16:24 malhal 30.8k7 gold badges122 silver badges150 bronze badges asked Feb 13 at 13:00 MallenowMallenow 133 bronze badges2 Answers
Reset to default 0The best way to ensure annotations are above all bars is to draw them separately. Instead of adding .annotation() inside the BarMark, create a second Chart layer on top (cleaner even if zIndex would be available).
Chart {
// First layer: Bars (rendered first, in background)
ForEach(model.data, id: \.text) { item in
BarMark(
x: .value("", item.text),
y: .value("consumption", item.consumption ?? 0),
width: .automatic
)
.foregroundStyle(LinearGradient(
gradient: Gradient(colors: gradientColors),
startPoint: .top,
endPoint: .bottom
))
.cornerRadius(4)
}
// Second layer: Annotations (rendered last, on top)
ForEach(model.data, id: \.text) { item in
if let selectedItem = selectedItem, selectedItem.text == item.text, let consumption = selectedItem.consumption, consumption > 0 {
PointMark(
x: .value("", item.text),
y: .value("consumption", item.consumption ?? 0)
)
.annotation(position: .top, alignment: .center) {
TooltipView(value: "\(selectedItem.text) • \(consumption)l")
.opacity(1)
.offset(y: -2)
}
}
}
// Third layer: RuleMark (always on top of bars)
if let average = model.average, average > 0 {
RuleMark(y: .value("", average))
.foregroundStyle(.primarySwiftUI)
.lineStyle(.init(lineWidth: 1))
}
}
chartAnnotation
is not suitable for this. I'd suggest putting the tooltip in the chartOverlay
instead. You can use the ChartProxy
you get to find the coordinates of the top of the bar, and position your view there.
Here is a complete example, where I just use position
to position the view. For a tooltip, you should probably move it up a bit with offset
.
struct Bar: Identifiable {
let x: String
let y: Double
var id: String { x }
}
struct ContentView: View {
let values = [
Bar(x: "A", y: 10),
Bar(x: "B", y: 1),
Bar(x: "C", y: 10),
Bar(x: "D", y: 1),
Bar(x: "E", y: 10),
Bar(x: "F", y: 1),
Bar(x: "G", y: 10),
Bar(x: "H", y: 1),
Bar(x: "I", y: 10),
Bar(x: "J", y: 1),
Bar(x: "K", y: 10),
Bar(x: "L", y: 1),
Bar(x: "M", y: 10),
]
@State private var selectedItem: Bar?
var body: some View {
Chart {
ForEach(values) { value in
BarMark(
x: .value("X", value.x),
y: .value("Y", value.y)
)
}
}
.chartOverlay { pr in
GeometryReader { geoProxy in
ZStack {
Rectangle()
.fill(.clear).contentShape(Rectangle()).padding([.leading, .trailing], 16)
.onTapGesture(perform: { value in
let origin = geoProxy[pr.plotAreaFrame].origin
let location = CGPoint(x: value.x - origin.x, y: value.y - origin.y)
if let selected = pr.value(atX: location.x, as: String.self),
let dataItem = values.first(where: { $0.x == selected }) {
self.selectedItem = dataItem
} else {
self.selectedItem = nil
}
})
if let selectedItem,
let x = pr.position(forX: selectedItem.x),
let y = pr.position(forY: selectedItem.y) {
Text("Some Looooooong Tooltip Text")
.background(.red)
.position(x: x, y: y)
}
}
}
}
}
}
A more sophisticated way to position the tooltip would be to align its bottom center at the top left of the chart, and then offset
it using the x and y coordinates from the ChartProxy
.
Text("Some Looooooong Text")
.background(.red)
.alignmentGuide(HorizontalAlignment.leading) { $0[HorizontalAlignment.center] }
.alignmentGuide(VerticalAlignment.top) { $0[.bottom] }
.offset(x: x, y: y - 10) // 10pts above the bar
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
本文标签: iosSwiftUI Chart BarMark Annotation covered by following BarMarksStack Overflow
版权声明:本文标题:ios - SwiftUI Chart BarMark Annotation covered by following BarMarks - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741539494a2384237.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论