From 1105383e079bad33e48e153abc3bfef920719aba Mon Sep 17 00:00:00 2001 From: Trey t Date: Mon, 22 Dec 2025 14:18:07 -0600 Subject: [PATCH] Replace ChartsPackage with native Swift Charts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rewrite HeaderStatsView using SwiftUI Charts framework - Delete unused GraphView.swift (only used in previews) - Remove unused `import Charts` from DayView.swift - Remove ChartsPackage SPM dependency from project - Native Swift Charts is simpler and has no external dependencies 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- Feels.xcodeproj/project.pbxproj | 17 -- .../xcshareddata/swiftpm/Package.resolved | 11 +- Shared/Views/DayView/DayView.swift | 1 - Shared/Views/GraphView.swift | 145 ------------------ Shared/Views/HeaderStatsView.swift | 143 ++++++----------- 5 files changed, 47 insertions(+), 270 deletions(-) delete mode 100644 Shared/Views/GraphView.swift diff --git a/Feels.xcodeproj/project.pbxproj b/Feels.xcodeproj/project.pbxproj index e9d8f17..760c2a9 100644 --- a/Feels.xcodeproj/project.pbxproj +++ b/Feels.xcodeproj/project.pbxproj @@ -9,7 +9,6 @@ /* Begin PBXBuildFile section */ 1C0DAB51279DB0FB003B1F21 /* Feels/Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 1C0DAB50279DB0FB003B1F21 /* Feels/Localizable.xcstrings */; }; 1C0DAB52279DB0FB003B1F22 /* Feels/Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 1C0DAB50279DB0FB003B1F21 /* Feels/Localizable.xcstrings */; }; - 1C9566422EF8F5F70032E68F /* Charts in Frameworks */ = {isa = PBXBuildFile; productRef = 1C9566412EF8F5F70032E68F /* Charts */; }; 1C9566442EF8F5F70032E68F /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = 1C9566432EF8F5F70032E68F /* Algorithms */; }; 1CB4D0A028787D8A00902A56 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CB4D09F28787D8A00902A56 /* StoreKit.framework */; }; 1CD90B07278C7DE0001C4FEA /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B06278C7DE0001C4FEA /* Tests_iOS.swift */; }; @@ -167,7 +166,6 @@ files = ( 1C9566442EF8F5F70032E68F /* Algorithms in Frameworks */, 1CD90B6C278C7F78001C4FEA /* CloudKit.framework in Frameworks */, - 1C9566422EF8F5F70032E68F /* Charts in Frameworks */, 1CB4D0A028787D8A00902A56 /* StoreKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -311,7 +309,6 @@ ); name = "Feels (iOS)"; packageProductDependencies = ( - 1C9566412EF8F5F70032E68F /* Charts */, 1C9566432EF8F5F70032E68F /* Algorithms */, ); productName = "Feels (iOS)"; @@ -463,7 +460,6 @@ ); mainGroup = 1CD90AE5278C7DDF001C4FEA; packageReferences = ( - 1C95663F2EF8F4A90032E68F /* XCRemoteSwiftPackageReference "ChartsPackage" */, 1C9566402EF8F4D30032E68F /* XCRemoteSwiftPackageReference "swift-algorithms" */, ); productRefGroup = 1CD90AF6278C7DE0001C4FEA /* Products */; @@ -1124,14 +1120,6 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 1C95663F2EF8F4A90032E68F /* XCRemoteSwiftPackageReference "ChartsPackage" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/akatreyt/ChartsPackage"; - requirement = { - branch = master; - kind = branch; - }; - }; 1C9566402EF8F4D30032E68F /* XCRemoteSwiftPackageReference "swift-algorithms" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/apple/swift-algorithms.git"; @@ -1143,11 +1131,6 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 1C9566412EF8F5F70032E68F /* Charts */ = { - isa = XCSwiftPackageProductDependency; - package = 1C95663F2EF8F4A90032E68F /* XCRemoteSwiftPackageReference "ChartsPackage" */; - productName = Charts; - }; 1C9566432EF8F5F70032E68F /* Algorithms */ = { isa = XCSwiftPackageProductDependency; package = 1C9566402EF8F4D30032E68F /* XCRemoteSwiftPackageReference "swift-algorithms" */; diff --git a/Feels.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Feels.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 58a0a41..c5e3a4b 100644 --- a/Feels.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Feels.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,15 +1,6 @@ { - "originHash" : "e45fbe19a7a507ebcf572c1b8f6c29186a9e9e533901421771ee77df0ed47fc9", + "originHash" : "86635982bfc3fdf16c7186cd33e77b4dc10a24ff9127aaee5f4f0a3676ae2966", "pins" : [ - { - "identity" : "chartspackage", - "kind" : "remoteSourceControl", - "location" : "https://github.com/akatreyt/ChartsPackage", - "state" : { - "branch" : "master", - "revision" : "4eb26e270cba2a2230d9c6b25c2ad629bc215e15" - } - }, { "identity" : "swift-algorithms", "kind" : "remoteSourceControl", diff --git a/Shared/Views/DayView/DayView.swift b/Shared/Views/DayView/DayView.swift index 8d0cbe7..5806ca1 100644 --- a/Shared/Views/DayView/DayView.swift +++ b/Shared/Views/DayView/DayView.swift @@ -7,7 +7,6 @@ import SwiftUI import SwiftData -import Charts import TipKit struct DayViewConstants { diff --git a/Shared/Views/GraphView.swift b/Shared/Views/GraphView.swift deleted file mode 100644 index c49d27c..0000000 --- a/Shared/Views/GraphView.swift +++ /dev/null @@ -1,145 +0,0 @@ -// -// GraphView.swift -// Feels -// -// Created by Trey Tartt on 1/8/22. -// - -import Foundation -import SwiftUI -import Charts - -struct GraphView: View { - var body: some View { - ZStack { - Color(UIColor.secondarySystemBackground) - VStack { - VStack { - HStack { - ZStack { - Color(UIColor.systemBackground) - BarChartGraph(entries: [ - BarChartDataEntry(x: 1, y: Double(Int.random(in: 0...10))), - BarChartDataEntry(x: 2, y: Double(Int.random(in: 0...10))), - BarChartDataEntry(x: 3, y: Double(Int.random(in: 0...10))), - BarChartDataEntry(x: 4, y: Double(Int.random(in: 0...10))), - BarChartDataEntry(x: 5, y: Double(Int.random(in: 0...10))) - ]) - } - .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) - .padding() - - ZStack { - Color(UIColor.systemBackground) - BarChartGraph(entries: [ - BarChartDataEntry(x: 1, y: Double(Int.random(in: 0...10))), - BarChartDataEntry(x: 2, y: Double(Int.random(in: 0...10))), - BarChartDataEntry(x: 3, y: Double(Int.random(in: 0...10))), - BarChartDataEntry(x: 4, y: Double(Int.random(in: 0...10))), - BarChartDataEntry(x: 5, y: Double(Int.random(in: 0...10))) - ]) - } - .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) - .padding() - } - - ZStack { - Color(UIColor.systemBackground) - BarChartGraph(entries: [ - BarChartDataEntry(x: 1, y: Double(Int.random(in: 0...10))), - BarChartDataEntry(x: 2, y: Double(Int.random(in: 0...10))), - BarChartDataEntry(x: 3, y: Double(Int.random(in: 0...10))), - BarChartDataEntry(x: 4, y: Double(Int.random(in: 0...10))), - BarChartDataEntry(x: 5, y: Double(Int.random(in: 0...10))) - ]) - } - .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) - .padding() - - ZStack { - Color(UIColor.systemBackground) - BarChartGraph(entries: [ - BarChartDataEntry(x: 1, y: Double(Int.random(in: 0...10))), - BarChartDataEntry(x: 2, y: Double(Int.random(in: 0...10))), - BarChartDataEntry(x: 3, y: Double(Int.random(in: 0...10))), - BarChartDataEntry(x: 4, y: Double(Int.random(in: 0...10))), - BarChartDataEntry(x: 5, y: Double(Int.random(in: 0...10))) - ]) - } - .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) - .padding() - } - } - } - } -} - -struct BarChartGraph: UIViewRepresentable { - //Bar chart accepts data as array of BarChartDataEntry objects - var entries : [BarChartDataEntry] - - // this func is required to conform to UIViewRepresentable protocol - func makeUIView(context: Context) -> BarChartView { - //crate new chart - let chart = BarChartView() - chart.drawGridBackgroundEnabled = false - chart.drawValueAboveBarEnabled = false - - chart.xAxis.drawAxisLineEnabled = false - chart.xAxis.labelTextColor = .clear - - chart.rightAxis.drawAxisLineEnabled = false - chart.rightAxis.labelTextColor = .clear - - chart.leftAxis.drawAxisLineEnabled = false - chart.leftAxis.labelTextColor = .clear - - chart.xAxis.drawGridLinesEnabled = false - chart.leftAxis.drawGridLinesEnabled = false - chart.leftAxis.axisLineColor = .clear - chart.rightAxis.axisLineColor = .clear - - chart.legend.textColor = .clear - chart.legend.enabled = false - - chart.drawBordersEnabled = false - chart.drawMarkers = false - // chart.yAxis.drawGridLinesEnabled = false - chart.rightAxis.drawGridLinesEnabled = false - chart.borderColor = .clear - //it is convenient to form chart data in a separate func - chart.data = addData() - return chart - } - - // this func is required to conform to UIViewRepresentable protocol - func updateUIView(_ uiView: BarChartView, context: Context) { - //when data changes chartd.data update is required - uiView.data = addData() - } - - func addData() -> BarChartData{ - let data = BarChartData() - //BarChartDataSet is an object that contains information about your data, styling and more - let dataSet = BarChartDataSet(entries: entries) - // change bars color to green - dataSet.colors = [NSUIColor.green] - //change data label - data.append(dataSet) - return data - } - - typealias UIViewType = BarChartView -} - - -struct GraphView_Previews: PreviewProvider { - static var previews: some View { - Group { - GraphView() - - GraphView() - .preferredColorScheme(.dark) - } - } -} diff --git a/Shared/Views/HeaderStatsView.swift b/Shared/Views/HeaderStatsView.swift index d2bf222..14e626d 100644 --- a/Shared/Views/HeaderStatsView.swift +++ b/Shared/Views/HeaderStatsView.swift @@ -8,20 +8,19 @@ import SwiftUI import Charts -struct HeaderStatsView : UIViewRepresentable { - //Bar chart accepts data as array of BarChartDataEntry objects - var entries : [BarChartDataEntry] - var moodTints: [Color] - var textColor: Color - - var tmpHolderToMakeViewDiffefrent: Color - +struct MoodBarData: Identifiable { + let id = UUID() + let mood: Mood + let count: Int + let color: Color +} + +struct HeaderStatsView: View { + let barData: [MoodBarData] + let textColor: Color + init(fakeData: Bool, backDays: Int, moodTint: [Color], textColor: Color) { - self.moodTints = moodTint self.textColor = textColor - assert(moodTints.count == 5, "mood tint count should be 5") - self.tmpHolderToMakeViewDiffefrent = Color.random() - entries = [BarChartDataEntry]() var moodEntries: [MoodEntryModel]? @@ -30,106 +29,56 @@ struct HeaderStatsView : UIViewRepresentable { } else { guard let daysAgoDate = Calendar.current.date(byAdding: .day, value: -backDays, to: Date()), let daysAgo = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: daysAgoDate) else { + self.barData = [] return } - moodEntries = DataController.shared.getData(startDate: daysAgo, endDate: Date(), includedDays: [1,2,3,4,5,6,7]) } + + var data: [MoodBarData] = [] if let moodEntries = moodEntries { for (index, mood) in Mood.allValues.enumerated() { - entries.append( - BarChartDataEntry(x: Double(index + 1), - y: Double(moodEntries.filter({ - Int($0.moodValue) == mood.rawValue - }).count)) - ) + let count = moodEntries.filter { Int($0.moodValue) == mood.rawValue }.count + let color = index < moodTint.count ? moodTint[index] : .gray + data.append(MoodBarData(mood: mood, count: count, color: color)) } } + self.barData = data } - - // this func is required to conform to UIViewRepresentable protocol - func makeUIView(context: Context) -> BarChartView { - //crate new chart - let chart = BarChartView() - chart.drawGridBackgroundEnabled = false - chart.drawValueAboveBarEnabled = false - chart.xAxis.drawAxisLineEnabled = false - chart.xAxis.labelTextColor = .clear - - chart.rightAxis.drawAxisLineEnabled = false - chart.rightAxis.labelTextColor = .clear - - chart.leftAxis.drawAxisLineEnabled = false - chart.leftAxis.labelTextColor = .clear - - chart.xAxis.drawGridLinesEnabled = false - chart.leftAxis.drawGridLinesEnabled = false - chart.rightAxis.drawGridLinesEnabled = false - - chart.leftAxis.axisLineColor = .clear - chart.rightAxis.axisLineColor = .clear - - chart.legend.textColor = .clear - chart.legend.enabled = false - - chart.drawBordersEnabled = false - chart.drawMarkers = false - chart.borderColor = .clear - - chart.doubleTapToZoomEnabled = false - chart.leftAxis.axisMinimum = 0 - - chart.minOffset = 0 - - let data = BarChartData() - let dataSet = dataSet() - data.append(dataSet) - - chart.data = data - dataSet.valueFormatter = DefaultValueFormatter(decimals: 0) - - return chart - } - - // this func is required to conform to UIViewRepresentable protocol - func updateUIView(_ uiView: BarChartView, context: Context) { - let data = BarChartData() - let dataSet = dataSet() - data.append(dataSet) - uiView.data = data - - dataSet.valueFormatter = DefaultValueFormatter(decimals: 0) - } - - func dataSet() -> BarChartDataSet { - let dataSet = BarChartDataSet(entries: entries) - - // change bars color to green - dataSet.colors = moodTints.map({ NSUIColor( $0 ) }) - dataSet.secondaryTextColor = UIColor(textColor) - dataSet.valueColors = [UIColor(textColor)] - dataSet.highlightAlpha = 0.0 - dataSet.roundedCornerValue = 10 - - if let descriptor = UIFontDescriptor.preferredFontDescriptor( - withTextStyle: .title1).withSymbolicTraits([.traitBold]) { - dataSet.valueFont = UIFont(descriptor: descriptor, size: 0) - } else { - dataSet.valueFont = UIFont.preferredFont(forTextStyle: .title1) + var body: some View { + Chart(barData) { item in + BarMark( + x: .value("Mood", item.mood.widgetDisplayName), + y: .value("Count", item.count) + ) + .foregroundStyle(item.color) + .cornerRadius(10) + .annotation(position: .top) { + Text("\(item.count)") + .font(.title.bold()) + .foregroundColor(textColor) + } } - - return dataSet + .chartXAxis(.hidden) + .chartYAxis(.hidden) + .chartLegend(.hidden) + .chartYScale(domain: 0...(maxCount + 1)) + } + + private var maxCount: Int { + max(barData.map(\.count).max() ?? 1, 1) } - - typealias UIViewType = BarChartView - } - - struct HeaderStatsView_Previews: PreviewProvider { static var previews: some View { - HeaderStatsView(fakeData: true, backDays: 30, moodTint: [Color.green, Color.blue, Color.yellow, Color.red, Color.orange], textColor: .white).frame(minHeight: 85, maxHeight: 90) + HeaderStatsView( + fakeData: true, + backDays: 30, + moodTint: [Color.green, Color.blue, Color.yellow, Color.red, Color.orange], + textColor: .white + ) + .frame(minHeight: 85, maxHeight: 90) } }