Im trying to resolve the animated transition to the correct one BarMark using Charts library.
The first idea was to add to each element id and next using .scrollTo(id) from ScrollViewReader. Unfortunately, it doesn't work.
import SwiftUI
import UIKit
import Charts
struct PartyItem: Identifiable, Codable {
var type: String
var count: Double
var color: Color
var id = UUID()
static var shared: [Self] = [
.init(type: "logo_1", count: 1, color: .blue),
.init(type: "logo_2", count: 1, color: .orange),
.init(type: "logo_3", count: 1, color: .purple),
.init(type: "logo_4", count: 1, color: .yellow),
.init(type: "logo_5", count: 1, color: .red),
.init(type: "logo_6", count: 1, color: .green),
.init(type: "logo_7", count: 1, color: .cyan),
.init(type: "logo_8", count: 1, color: .indigo)
]
}
extension PartyItem: Comparable {
static func < (lhs: PartyItem, rhs: PartyItem) -> Bool {
lhs.count < rhs.count
}
static func == (lhs: PartyItem, rhs: PartyItem) -> Bool {
lhs.count == rhs.count
}
}
enum OperationType {
case subtract, add
}
struct MainVoteView: View {
@AppStorage(.partyStats) private var partyStats: [PartyItem] = []
typealias ChartLocation = (location: CGPoint, proxy: ChartProxy, geometry: GeometryProxy)?
@State private var chartLocation: ChartLocation = nil
@State private var showingPopup = false
@State private var scaleValue: CGFloat = 1
var body: some View {
ScrollView(.horizontal) {
VStack {
Chart {
ForEach(partyStats.sorted(by: >)) { shape in
BarMark(
x: .value("Party Type", shape.type),
y: .value("Vote Count", shape.count)
)
.annotation(position: .bottom, alignment: .center, spacing: .spacingMedium) {
Image(shape.type)
.resizable()
.scaledToFit()
.frame(width: .sizeMediumMediumSmall, height: .sizeMediumMediumSmall)
}
.annotation(position: .top, alignment: .center, spacing: .spacingSmall, content: { value in
Text("\(Int(shape.count))")
.bold()
.font(.subheadline)
.foregroundColor(.white)
})
.foregroundStyle(shape.color)
}
}
.scaleEffect(scaleValue)
.animation(.easeInOut(duration: 0.5), value: scaleValue)
.frame(width: .sizeExtraExtraLarge)
.chartYAxis(.hidden)
.chartXAxis(.hidden)
.chartLegend(.hidden)
.padding()
.chartOverlay { proxy in
GeometryReader { geometry in
Rectangle().fill(.clear).contentShape(Rectangle()).onTapGesture { location in
scaleValue = 1.1
showingPopup = true
chartLocation = (location, proxy, geometry)
}
}
}
}
}
.scrollIndicators(.hidden)
.padding()
.background(Color.darkBlue)
.navigationBarBackButtonHidden()
.popup(isPresented: $showingPopup) {
PopUpView {
scaleValue = 1
updateSelected(at: chartLocation, operation: .add)
} nayAction: {
scaleValue = 1
updateSelected(at: chartLocation, operation: .subtract)
} abstainAction: {
scaleValue = 1
}
}
}
}
extension MainVoteView {
private func updateSelected(at chartLocation: ChartLocation, operation: OperationType) {
guard let location = chartLocation?.location,
let proxy = chartLocation?.proxy,
let geometry = chartLocation?.geometry else {
return
}
let xPosition = location.x - geometry[proxy.plotAreaFrame].origin.x
guard let selected: String = proxy.value(atX: xPosition) else {
return
}
guard let index = partyStats.enumerated().first(where: { $0.element.type == selected })?.offset else {
return
}
switch operation {
case .subtract:
if partyStats[index].count > 0 {
partyStats[index].count -= 1
}
case .add:
partyStats[index].count += 1
}
}
}
And when it updates the partyStats value, I'd like to center the screen where it's placed on the screen. For example: When i want update value of blue mark bar I would like to scroll to where it will go. The data is sorted from largest to smallest,