Finishing our app

Selecting different time frames ⏱

Last but not least, let’s add a time frame bar into our ContentView that we can use to switch between a weekly and daily time frame for our Chart.

To do this, add a State property to your ContentView (where 0 represents daily and 1 represents weekly).

@State var timeFrameChoice = 0

Next, insert the following struct below your ContentView struct …

struct TimeFrameBar: View {
    
    @Binding var timeFrameChoice: Int
    
    var body: some View {
        HStack {
            Text("Day")
                .font(.custom("Avenir", size: 18))
                .fontWeight(timeFrameChoice == 0 ? .medium : .none)
                .foregroundColor(timeFrameChoice == 0 ? .blue : .gray)
                .onTapGesture(perform: {self.timeFrameChoice = 0})
            Text("Week")
                .font(.custom("Avenir", size: 18))
                .fontWeight(timeFrameChoice == 1 ? .medium : .none)
                .foregroundColor(timeFrameChoice == 1 ? .blue : .gray)
                .onTapGesture(perform: {self.timeFrameChoice = 1})
            Spacer()
        }
            .padding()
    }
}

… and initialize the TimeFrameBar view inside your ContentView right above the Header and Chart view:

VStack {
    TimeFrameBar(timeFrameChoice: $timeFrameChoice)
    //...
}

Your ContentView preview should now look like this:

Depending on the selected time frame, we want our Chart and Header to use a different array containing only the specific entries. For this purpose, we need to add two more arrays to our DownloadManager.

var weeklyEntries = [DataEntry]()
var dailyEntries = [DataEntry]()

Thus, when fetching the stock data, we need to return weekly and daily entries as well.

private func downloadJSON(stockSymbol: String) async throws -> (dataEntries: [DataEntry], dailyEntries: [DataEntry], weeklyEntries: [DataEntry]) {

    dataEntries = [DataEntry]()
    dailyEntries = [DataEntry]()
    weeklyEntries = [DataEntry]()
            
    //...
        
    return (dataEntries, dailyEntries, weeklyEntries)

}

Let’s change the Task in our fetchData function correspondingly.

Task {
    (dataEntries, dailyEntries, weeklyEntries) = try await downloadJSON(stockSymbol: stockSymbol)
     DispatchQueue.main.async {
         withAnimation {
             self.dataFetched = true
         }
    }
}

Next, we need to adjust how we filter the data in our downloadJSON function. Delete the filteredEntries variable and append the data filtered using the isInLastNDays method to the declared weeklyEntries array.

private func downloadJSON(stockSymbol: String) async throws -> (dataEntries: [DataEntry], dailyEntries: [DataEntry], weeklyEntries: [DataEntry]) {

    //...
        
   guard let lastDateOfData = dataEntries.last?.date else { throw FetchError.badEntries }
        
    for entry in dataEntries {
        if entry.date.isInLastNDays(lastDate: lastDateOfData, dayRange: 7) {
            weeklyEntries.append(entry)
        }
    }
        
    return (dataEntries, dailyEntries, weeklyEntries)

}

While cycling through the dataEntries we append all entries from the same day by adding the following conditional block to the for-in loop:

for entry in dataEntries {
    if Calendar.current.isDate(entry.date, equalTo: lastDateOfData, toGranularity: .day) {
        dailyEntries.append(entry)
    }
    //...
}

Perfect! Back in our ContentView, we can now either use the weeklyEntries or the dailyEntries array when initializing the Chart and Header depending on the timeFrameChoice

if downloadManager.dataFetched {
    Header(stockData: timeFrameChoice == 0 ? downloadManager.dailyEntries : downloadManager.weeklyEntries)
    Chart(dataSet: timeFrameChoice == 0 ? downloadManager.dailyEntries : downloadManager.weeklyEntries)
        .frame(height: 300)
}

Finally, we need to tell our StockListRow view to use the dailyEntries array of its downloadManager to calculate the percentage change.

if downloadManager.dataFetched {
    VStack(alignment: .trailing) {
        Text(String(format: "%.2f", getPercentageChange(stockData: downloadManager.dailyEntries)) + "%")
        //...
       .foregroundColor(getPercentageChange(stockData: downloadManager.dailyEntries) < 0 ? .red : .green)
        Text("$" + String(format: "%.2f", downloadManager.dailyEntries.last?.close ?? 0))
        //...
    }
}

Let’s run our app to see if that works. Awesome! We’re now able to switch between a daily and weekly time frame by using the related bar.

Finishing our app ✅

Let’s finish our app by adding a dynamic navigation bar title to each ContentView indicating the currently represented stock. To do this, add a new property to your ContentView called “stockSymbol”.

let stockSymbol: String

Next, use the String assigned to the stockSymbol for the .navigationTitle modifier.

VStack {
    //...
}
    .navigationTitle(stockSymbol)

In our ContentView_Previews struct, we add the corresponding stockSymbol argument and enter a sample String.

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(stockSymbol: "AAPL", downloadManager: DownloadManager(stockSymbol: "AAPL"))
    }
}

We also need to add the stockSymbol parameter when initializing the ContentView in our StockListRow. For this purpose, declare a corresponding property in the StockListRow struct as well. While doing this, also add a property named “stockName”.

let stockSymbol: String
let stockName: String

Now we can use the stockSymbol when initializing the ContentView inside the StockListRow. Additionally, we use the stockSymbol and stockName instead of current placeholder Text views.

NavigationLink(destination: ContentView(stockSymbol: stockSymbol, downloadManager: downloadManager)) {
    VStack(alignment: .leading) {
        Text(stockSymbol)
            .font(.custom("Avenir", size: 20))
            .fontWeight(.medium)
        Text(stockName)
            .font(.custom("Avenir", size: 16))
    }
        //...
})

Finally, we provide our StockListRow instance inside our StockList view with the stock’s name and symbol.

List {
    StockListRow(stockSymbol: "AAPL", stockName: "Apple, Inc.", downloadManager: DownloadManager(stockSymbol: "AAPL"))
}

If we run the app now and navigate to the ContentView, you see that the navigation bar displays the provided stock symbol.

That’s it! If you want, you can also add some other StockListRow instances into the StockList by providing each one with the stockName and specific stockSymbol (Just google “Stock Symbol *Stock Name* stock symbol to find the right one.)!

Conclusion 🎊

Great, we finished our StockX app!

You have learned a lot in this chapter. First, we have dealt with how to draw our own shapes in SwiftUI. Then, we used this knowledge to prepare data visually in charts.

Finally, we used the Alpha Vantage API to provide our app with real-life stock data. 

As a suggestion, you can try to add more time frame options to the app or display date information on the charts’ x-axis.

Leave a Reply

Your email address will not be published. Required fields are marked *