[iOS] Expense Tracker. Part 4 Charts.

MYH
彼得潘的 Swift iOS App 開發教室
7 min readAug 26, 2021

--

Finally, the final part of my project. In this article, I will discuss about Charts. Flag on the play. I had already written an article on Charts in my previous post where I talked about how to make a line chart based on the data you received from networking API. This one.

Thus, I won’t talk about the line chart; instead, I’ll focus on the pie chart (actually doesn’t make that much of a difference) and how I read the data from Firebase and calculate the total on each category (This is the part I really want to share with you guys.) At the end of this article, I’ll paste the link to my github project. Alright, without further ado, let’s dive in.

Pie Chart
How to read data from Firestore database and calculate the total expense of each category
DispatchGroup

Pie Chart

Ok, here we go again. The standard procedure.

  1. open terminal and type cd + file directory
  2. pod init
  3. go to your podfile and write pod ‘Charts’
  4. close the Xcode project and run pod install
  5. open the .xcworkspace file and remember to import Charts

Create a view in your main.storyboard and subclass it to PieChartView.

Link your view to the viewController to make it an IBOutlet. And the rest of the steps are quite similar to making a line chart view.

  1. set your DataEntry. This is where you store the data. Use PieChartDataEntry(value: Double, label: String) to do the trick.
let pieChartEntry: [PieChartDataEntry] = [            PieChartDataEntry(value: Double(foodTotal), label: ExpenseConstant.food), 
PieChartDataEntry(value: Double(somethingTotal), label: "something")]

2. Set the DataSet. This is where you can alter the color, font, etc. The “colors” attribute will be an array of colors which would be filled according to the sequence of your DataEntry.

let set = PieChartDataSet(entries: pieChartEntry)set.colors = [UIColor.systemRed, UIColor.systemWhite]
set.selectionShift = 5
set.sliceSpace = 3

3.Set the Data. This is the object that would be added to the view.

let data = PieChartData(dataSet: set)

4. Pass the Data to your view.

pieChartView.data = data

And here’s the complete version of my code.

How to read data from Firestore database and calculate the total expense of each category.

In my previous article on FSCalendar, I mentioned about how to read data from Firestore database and calculate the total expense of a certain date. Now you might think it’s the same thing with just some simple changes here and there. Well...it’s not. Let’s take a look.

I want the pie chart to show the expense total of each category of the last 7 days. To do this, I have to first create an array of date of the last 7 days. Here’s how I do it.

let date = Date()let formatter = DateFormatter()let datecomponent = DateComponents()var last7days = [String]()for i in -6...0 {datecomponent.day = ilet day = Calendar.current.date(byAdding: datecomponent, to: date)!formatter.dateStyle = .shortformatter.timeStyle = .nonelet stringDate = formatter.string(from: day)last7days.append(stringDate)}

After I have the date of the last 7 days, I can use a for-loop to read the data in each day, and it would look like this.

let db = Firestore.firestore()func loadData(in days:[String]) {  for day in days {      db.collection("collection")        .whereField("date", isEqualTo: day)        .getDocuments {

//calculate the total here in this closure.
} }}

To calculate the total expense of different categories, I wrote a new dictionary to store each category’s value. Like this.

Now that’s combine this dictionary with the Firestore closure.

var expenseCatData = [ExpenseTotalCategory]()for day in days {   var total: Int = 0   db.collection(ExpenseConstant.collection)     .whereField(ExpenseConstant.date, isEqualTo: day)     .getDocuments { querysnapshot, error in   if let e = error {    print("Error retrieving data from firebase. \(e)")   } else {   if let query = querysnapshot?.documents {   for doc in query {   let data = doc.data()   if let expense = data[ExpenseConstant.expense] as? String, let category = data[ExpenseConstant.category] as? String {   if let intN = Int(expense) {   let newCatDat = ExpenseTotalCategory(category: category, number: intN)   self.expenseCatData.append(newCatDat)       } else {   print("Error converting string to Int")     }   } else {  print("Error converting Any to String?")   }  }}

We want something like this — an array of ExpenseTotalCategory dictionary.

However, this was far from finished. It did become an array of dictionary which record every expense and its category, but we want to calculate the total of each category. I did this by using for-loop and switch.

func updatePieChart(total: [ExpenseTotalCategory]) {  var foodTotal: Int = 0  var educationTotal: Int = 0  var entertainmentTotal: Int = 0  var pharmacyTotal: Int = 0  var homeTotal: Int = 0  var commuteTotal: Int = 0  var moreTotal: Int = 0  var shoppingTotal: Int = 0  for data in total {    switch data.category {    case ExpenseConstant.food:      foodTotal += data.number    case ExpenseConstant.education:      educationTotal += data.number    case ExpenseConstant.entertainment:      entertainmentTotal += data.number    case ExpenseConstant.pharmacy:      pharmacyTotal += data.number    case ExpenseConstant.commute:      commuteTotal += data.number    case ExpenseConstant.home:      homeTotal += data.number    case ExpenseConstant.shopping:      shoppingTotal += data.number    case ExpenseConstant.more:      moreTotal += data.number    default:      print("Error recognizing data category")    }  }}

Now that’s combine everything. After I load the data from Firestore database, I pass the data to the function that I wrote to update pie chart.

However, it’s still not over. If I run the project right now, my chart view won’t show anything.

Let’s take a look at our console and see what’s wrong? (I printed out everything so that I can easily tell the process.)

You would find out that the data got passed in to the function that update the pie chart is empty even though the code was supposed to fetch the data from Firestore database before it got passed down. The reason behind this is because our code won’t wait for the data fetching, in other word, our code is asynchronous. To fix this, we need to learn about DispatchGroup.

DispatchGroup

Here is an amazing article that explains why asynchronous and synchronous would play such a major role in these kind of problems.

Tbh, I didn’t fully grasp the idea of concurrency, and I’m afraid that I will misguide you guys, so I’ll direct you to some really great articles.

Anyway, in my case, I was able to solve it using DispatchGroup. And the code look like this.

my github file: (I’ve ignored my firebase file, so if you want to download and run the project, you’ll have to set up your firebase first.)

--

--