[iOS] Expense Tracker. Part 4 Charts.

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.)

--

--

--

學習 Swift iOS App 開發的學生作品集

Recommended from Medium

Adding multiple projects in one Xcode project

Sharing Object Data Between an iOS App and Its Widget

Fetch and use API data in iOS

Create a Voice Changing Video Call App with SwiftUI

UITableView With Custom Cells + Details Screen — Part 2

ByteX-iOS App is Live

SwiftUI Tutorial: Working with Picker

UIKeyCommand — Part 3: macOS Catalyst Menu Items

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
MYH

MYH

iOS Rookie Developer 

More from Medium

Music Bars in iOS using Swift 5.0

Content Hugging & Content Compression Resistance Priority [iOS — CHCR priorities]

Ephemeral CI for iOS with GitHub Actions and Orka Just Got Better

WebEngage in iOS