1

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,

enter image description here

miltenkot
  • 29
  • 4
  • 1
    Where is the code where you actually try to scroll? Please try to create a [mre]. – burnsi Feb 18 '23 at 12:02
  • I updated the example. Popup view is just a modal which += or -+ counter. And I try focus on bar mark that jumped over screen. – miltenkot Feb 18 '23 at 13:17
  • Hard to understand what you are asking here. The title and the initial code contained a `ScrollViewReader` and you wanted to scroll. But the `ScrollViewReader` is gone now. So please clarify what `focus on bar mark that jumped over screen.` means. – burnsi Feb 18 '23 at 13:31
  • When I press on the blue bar, a modal appears where I decrease its value. Because charts is sorted the bigger one on the right jumps in its place. And after this operation i want focus on this decreased. its a little bit clearer? :) – miltenkot Feb 18 '23 at 14:01
  • 1
    Same problem here. in SwiftUI charts it’s not possible to set an id to mark items. Therefore it’s not possible to scroll via an id to an specific element. Is there any way to scroll to the end? – Philsen3000 Feb 27 '23 at 20:37

0 Answers0