Skip to content

Commit 0e00914

Browse files
feat: chart widget (#74)
1 parent 33e7e10 commit 0e00914

File tree

9 files changed

+159
-0
lines changed

9 files changed

+159
-0
lines changed

GoMoney.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
080ADD30290FB068005DCF86 /* DetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080ADD2F290FB068005DCF86 /* DetailView.swift */; };
1515
0816CEED292BE5BB00C17626 /* ViewAllViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0816CEEC292BE5BB00C17626 /* ViewAllViewController.swift */; };
1616
0816CEEF292BE66300C17626 /* ViewAllViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0816CEEE292BE66300C17626 /* ViewAllViewModel.swift */; };
17+
0816CEF1292C66FF00C17626 /* ChartWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0816CEF0292C66FF00C17626 /* ChartWidget.swift */; };
18+
0816CEF2292C68C600C17626 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 08C872AB28F655CF00DC859D /* Assets.xcassets */; };
1719
081CB3112906F5310007D426 /* LineChartXAxisFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 081CB3102906F5310007D426 /* LineChartXAxisFormatter.swift */; };
1820
081CB31429077BF20007D426 /* DayAxisValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 081CB31329077BF20007D426 /* DayAxisValueFormatter.swift */; };
1921
081CB3162907829C0007D426 /* Expense+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 081CB3152907829C0007D426 /* Expense+Extension.swift */; };
@@ -183,6 +185,7 @@
183185
080ADD2F290FB068005DCF86 /* DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailView.swift; sourceTree = "<group>"; };
184186
0816CEEC292BE5BB00C17626 /* ViewAllViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewAllViewController.swift; sourceTree = "<group>"; };
185187
0816CEEE292BE66300C17626 /* ViewAllViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewAllViewModel.swift; sourceTree = "<group>"; };
188+
0816CEF0292C66FF00C17626 /* ChartWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartWidget.swift; sourceTree = "<group>"; };
186189
081CB3102906F5310007D426 /* LineChartXAxisFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChartXAxisFormatter.swift; sourceTree = "<group>"; };
187190
081CB31329077BF20007D426 /* DayAxisValueFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayAxisValueFormatter.swift; sourceTree = "<group>"; };
188191
081CB3152907829C0007D426 /* Expense+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Expense+Extension.swift"; sourceTree = "<group>"; };
@@ -925,6 +928,7 @@
925928
isa = PBXGroup;
926929
children = (
927930
08EEA014291CD373003B35B8 /* IncomeWidget.swift */,
931+
0816CEF0292C66FF00C17626 /* ChartWidget.swift */,
928932
);
929933
path = IncomeWidget;
930934
sourceTree = "<group>";
@@ -1074,6 +1078,7 @@
10741078
isa = PBXResourcesBuildPhase;
10751079
buildActionMask = 2147483647;
10761080
files = (
1081+
0816CEF2292C68C600C17626 /* Assets.xcassets in Resources */,
10771082
08EEA000291CC29E003B35B8 /* Assets.xcassets in Resources */,
10781083
);
10791084
runOnlyForDeploymentPostprocessing = 0;
@@ -1260,6 +1265,7 @@
12601265
08EEA018291CD3E4003B35B8 /* UserDefaults+AppGroup.swift in Sources */,
12611266
08EEA016291CD397003B35B8 /* WidgetBundle.swift in Sources */,
12621267
08C0DB28292B6FA4005A8973 /* Double+Extension.swift in Sources */,
1268+
0816CEF1292C66FF00C17626 /* ChartWidget.swift in Sources */,
12631269
08EEA019291CD3E7003B35B8 /* Widget+Kind.swift in Sources */,
12641270
);
12651271
runOnlyForDeploymentPostprocessing = 0;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "chart.png",
5+
"idiom" : "universal",
6+
"scale" : "1x"
7+
},
8+
{
9+
"idiom" : "universal",
10+
"scale" : "2x"
11+
},
12+
{
13+
"idiom" : "universal",
14+
"scale" : "3x"
15+
}
16+
],
17+
"info" : {
18+
"author" : "xcode",
19+
"version" : 1
20+
}
21+
}
112 KB
Loading

GoMoney/Scences/Home/HomeViewController.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ class HomeViewController: GMMainViewController {
185185
incomeSum: viewModel.incomeSum,
186186
expenseSum: viewModel.expenseSum
187187
)
188+
189+
chartView.saveImage()
188190
}
189191

190192
// MARK: Methods

GoMoney/Scences/Home/View/ChartView.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,23 @@ class ChartView: UIView {
114114
required init?(coder _: NSCoder) {
115115
fatalError("init(coder:) has not been implemented")
116116
}
117+
118+
func saveImage() {
119+
let image = pieChartView.getChartImage(transparent: false)
120+
121+
guard
122+
let image = image,
123+
let data = image.jpegData(compressionQuality: 1) ?? image.pngData()
124+
else {
125+
return
126+
}
127+
guard let directory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.kappa.expense")?.appendingPathComponent("chart.png") as? URL else {
128+
return
129+
}
130+
do {
131+
try data.write(to: directory)
132+
} catch {
133+
print(error.localizedDescription)
134+
}
135+
}
117136
}

GoMoney/Service/WidgetService.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,8 @@ class WidgetService {
66
UserDefaults.appGroup.set(expense, forKey: UserDefaults.Keys.expense.rawValue)
77
WidgetCenter.shared.reloadTimelines(ofKind: WidgetKind.income)
88
}
9+
10+
func updateChartWidget() {
11+
WidgetCenter.shared.reloadTimelines(ofKind: WidgetKind.chart)
12+
}
913
}

GoMoney/ViewModel/Home/HomeViewModel.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class HomeViewModel {
3030
expenseSum = expenses.reduce(0) { $0 + $1.amount }
3131

3232
widgetService.updateIncomeWidget(income: incomeSum ?? 0, expense: expenseSum ?? 0)
33+
widgetService.updateChartWidget()
3334

3435
groupedExpenses = expenses.groupExpensesByTag()
3536
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import SwiftUI
2+
import WidgetKit
3+
4+
public extension UIImage {
5+
static func loadFrom(url: URL, completion: @escaping (_ image: UIImage?) -> Void) {
6+
DispatchQueue.global().async {
7+
if let data = try? Data(contentsOf: url) {
8+
DispatchQueue.main.async {
9+
completion(UIImage(data: data))
10+
}
11+
} else {
12+
DispatchQueue.main.async {
13+
completion(nil)
14+
}
15+
}
16+
}
17+
}
18+
}
19+
20+
private struct Provider: TimelineProvider {
21+
let icon = UIImage(named: "ic_chart") ?? UIImage()
22+
func placeholder(in _: Context) -> SimpleEntry {
23+
SimpleEntry(date: Date(), image: Image(uiImage: icon))
24+
}
25+
26+
func getSnapshot(in _: Context, completion: @escaping (SimpleEntry) -> Void) {
27+
let entry = SimpleEntry(date: Date(), image: Image(uiImage: icon))
28+
completion(entry)
29+
}
30+
31+
func getTimeline(in _: Context, completion: @escaping (Timeline<Entry>) -> Void) {
32+
let currentDate = Date()
33+
let nextDate = Calendar.current.date(byAdding: .day, value: 1, to: currentDate)
34+
35+
guard let nextDate = nextDate else {
36+
return
37+
}
38+
39+
guard let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.kappa.expense")?.appendingPathComponent("chart.png") else {
40+
return
41+
}
42+
43+
UIImage.loadFrom(url: url) { image in
44+
if let image = image {
45+
let date = Date()
46+
let img = Image(uiImage: image)
47+
48+
let entry: SimpleEntry
49+
50+
entry = SimpleEntry(
51+
date: date,
52+
image: img
53+
)
54+
55+
let timeline = Timeline(
56+
entries: [entry],
57+
policy: .after(nextDate)
58+
)
59+
60+
completion(timeline)
61+
62+
} else {
63+
let entry = SimpleEntry(date: currentDate, image: Image(uiImage: icon))
64+
let timeline = Timeline(entries: [entry], policy: .atEnd)
65+
completion(timeline)
66+
}
67+
}
68+
}
69+
}
70+
71+
private struct SimpleEntry: TimelineEntry {
72+
let date: Date
73+
let image: Image
74+
}
75+
76+
private struct ChartWidgetEntryView: View {
77+
var entry: Provider.Entry
78+
79+
var body: some View {
80+
ZStack {
81+
entry.image
82+
.resizable()
83+
.aspectRatio(contentMode: .fill)
84+
}
85+
}
86+
}
87+
88+
struct ChartWidget: Widget {
89+
private let kind: String = WidgetKind.chart
90+
91+
var body: some WidgetConfiguration {
92+
StaticConfiguration(kind: kind, provider: Provider()) { entry in
93+
ChartWidgetEntryView(entry: entry)
94+
}
95+
.configurationDisplayName("Chart Widget")
96+
.description("A pie chart show your expenses' percentages")
97+
.supportedFamilies([.systemSmall])
98+
}
99+
}

WidgetExtension/WidgetBundle.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@ import WidgetKit
33

44
@main
55
struct GMWidgetBundle: WidgetBundle {
6+
var body: some Widget {
7+
WidgetBundle1().body
8+
}
9+
}
10+
11+
struct WidgetBundle1: WidgetBundle {
612
var body: some Widget {
713
IncomeWidget()
14+
ChartWidget()
815
}
916
}

0 commit comments

Comments
 (0)