Learn Courses My Dashboard

Wildsparrow's journal (monthly challenges)

After successfully participating in the first CWC monthly challenge (weather app), I decided to participate in every monthly challenge. Thus, I will dedicate this journal to all current and future monthly challenges.

Let’s start with the May challenge: A stock tracker app.

Thinking about all the possibilities you could show with stocks and my current SwiftUI knowledge :smiley: , here is my plan for the app:

  • One page app. No pop ups, overlays or similar
  • No assets: All graphics should be done by using shapes and colors.
  • Showing the most relevant data of stocks in a tile, i.e. stock name, current price, day high/low, annual high/low
  • Add stock to tiles, removes stocks from tiles. Probably 5 tiles with stocks. When in removing mode, “-” should appear on the top right corner of the tiles. Nice to have: Tiles should shake during this process. For adding stocks: There should be a text field in a tile and a “+” sign.
  • (If I can figure out how) Graph showing the closing prices of the last week (maybe month or year - will decide during coding)

Layout:

2 Likes

Awesome plan. Looking forward to How you do it.

1 Like

I couldn’t spend a lot of time in the last couple of days for programming, but as I will be on vacation next week, I should be able to finish this project within the next couple of days.

For the background, I looked into this and adapted the code to my needs: https://youtu.be/YhhGx0pLOnk
The theme is based on the main colors of the Nasdaq, green, purple, blue.
Looks better in reality, but for easy showing I created a GIF which lacks some smooth color transitions.

The stock tiles (all read Apple at the moment) are scrollable horizontally.

For the line chart, I imported GitHub - AppPear/ChartView: ChartView made in SwiftUI and placed it on top of a black rectangle.

Things to do:

  • Get the data from a stock API (maybe https://marketstack.com )
  • Fill in the stock tiles with real data (creating an array with the “add ticker symbol” and “remove” buttons)
  • Pass the data to the line chart
  • Alter the line chart based on the weekly, monthly or yearly data
2 Likes

Initially, I wanted to use marketstack.com for the API stock data, but the free connection is only by http, not by https. I updated the Info.plist for this exception, but was not totally sure, if it was working. I then looked into the great solution from @chflorian and now I’m using AlphaVantage, too.

I used the full daily API and extracted the min/max of the last year (last 365 days; the API response will give to you more than 2 years of data). Furthermore I passed the data from the last week and the last month and the last year into arrays, so I can display them in the line chart.

Then, when the values are displayed in the tiles, I truncated them to the decimal point (no rounding) when they were passed as a string, and rounded them when they were passed as decimal.

For the line chart, I added another rectangle so that you can see the values when you slide over the line chart with your finger. The value is only displayed in black and I couldn’t find a parameter to change this.

Currently, I use the Demo Key for the API request, thus you only see fictional IBM data. In the sample below, you see the current value of IBM, the daily min/max and the annual min/max. In the chart, you see the closing values of the last 7 days. You may notice that the current day is on the left side, so I need to reverse the values in the array.

Things to do:

  • Reverse the values in the line chart array, so that the most current date is on the righthand side
  • Highlight the “week”, “month”, year" buttons, depending on what is seen on the line chart
  • Get the “add ticker symbol” and “remove” buttons to work

As this is working quite nicely, I will try to share my solution when it’s finished.

(keep in mind: the colors in the background blend without any boarders in the app, it’s just in the GIF that you can see the pixels)

Edit: Oh, the array reverse was reaaaaly easy.
var oneWeekCloseValues: [Double] {
var first7 = Array(closeValues.prefix(upTo: 7))
first7.reverse()
return first7
}

1 Like

Today was a good day. I figured out how to pass the data from the stock tile to the line chart.

So, now you can click on the stock tiles and the data from the last week, last month or last year is displayed (and of course the ticker symbol). The week/month/year buttons will change colors according to what is displayed.

Note that the stock tiles and week/month/year buttons are independent. If you select a weekly view and change to another stock, you will see the weekly view of this stock. If you select a monthly view and change to another stock, you will see the monthly view of this stock. Clicking on the stock tile will not set a default state like always the weekly view.

The last thing on my to-do list: Add / remove ticker symbols via the buttons. While adding is easy (just append the symbol to the array), for removing I want to have some visuals, but I will figure this out and create a nice way to display it. The code should be similar to the last monthly challenge.

Bonus:

  • Make sure that there is no duplicate in the stock array
  • Store the stock array in the user data, so that you will see your pre-selected stocks when closing and opening the app.
2 Likes

It’s time for a short update:

  • Adding stocks is working
  • Removing stocks is working, unfortunately I can only remove the latest item I added, not the one the user is tapping on. I tried a similar approach as the .onDelete() function in a list view, but with no success. So, after several failed attempts, I decided that for this challenge (and as this is the second app I write by myself in SwiftUI), the user can only delete the latest entry. But he/she gets a nice alert pop-up to see (AlertX, version 1.2.1 GitHub - neel-makhecha/AlertX: A library for SwiftUI to create custom alerts with different appearances and animations. ), including the name of the item he/she is about to remove (he/she can also cancel the request). So basically, I delete the array from the ending, always the last item is removed.

Big kudos to Alpha Vantage → I wrote an email to them, asking to increase the API requests/minute for this challenge and they did within 2-3 hours!

So, the two bonus challenges are still open: No duplicate in the stock array and storing the array in the user data.
Afterwards, I’ll create a video of my solution :slight_smile:

1 Like

So, I did it :wink:

Changes to the last code:

  • Instead of displaying 365 values in the graph for the yearly chart, the app is now displaying 52 values, each is the average stock close value of week 1, week 2, week 3 etc. To do so, I implemented a for … in loop over 52 iterations, each time calculating the average of the 7 close values per week
  • Not allowing duplicate entries: When the user wants to add a value which is already in the array, the entered value in my textField is simply deleted. Only new values are appended to the stock array.
  • Finally, I store the stock array in the UserDefaults, which is always stored after a successful adding of a stock or a deletion of a stock. (that’s just one line of code). When starting the app, the UserDefaults is read. If the array is empty (which can only happen if you open the app for the first time), 2 default stock values will be initialized automatically.

Overall, that was a nice learning experience. This time (as a good practice), I added lots of comments what the code is doing (or should do :wink: ). Should be helpful for the next challenge :slight_smile:

If anyone is interested in a detail of my code, feel free to ask and I post my solution. If anyone could point out how I can share the whole project, I will do so. But keep in mind: I’m a beginner, so my solution is probably not best practice.

I’ll post a video of my app tomorrow. Stay tuned :slight_smile:

1 Like

Have a look :slight_smile:

So, let’s start with the next challenge, the NBA stats app

or as I call it, the MLB stats app.

The NBA is already in its finals, so there are only two teams playing for the championship. Setting up an app with your favorite team is … well, not the ideal moment in time :wink:

In contrast, the MLB regular season runs till the beginning of October. So there are a lot of games to be played by all teams. There are also leagues and divisions, so filtering can also used for MLB.

Let’s recap the requirements:

MUSTS:

  • Retrieve data by API calls, using sportsdata.io
  • Display the current scores (today’s scores if games are scheduled)
  • Display past scores
  • Display upcoming games
  • Display the team logos of the pair of teams in every game

NICE TO HAVES:

  • Display the standings, separated in leagues/divisions
  • Let the user chose a favorite team and highlight that team in the scores and standing views
1 Like

I decided to go with the design proposal of the CWC solution:
TabView for scores, standings and settings
Colored segmented picker for past/today/upcoming games

To change the colors of the picker, I put in this code just before the “var body: some View” part:

//MARK: Modify Segemented Picker Style Colors
init() {
UISegmentedControl.appearance().selectedSegmentTintColor = .red
UISegmentedControl.appearance().backgroundColor = .blue
UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected)
UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.white], for: .normal)
}

2 Likes

Wow… you off to a good start :clap:t3:

1 Like

So, I spent more time in the foundation course than on the challenge recently.

I managed to get the API working and can display the latest results in a nice ScrollView.
For the pictures, I downloaded them all, converted them to PNG and added them to the asset list. I named them just as the homeTeamID / awayTeamID, which is basically just a number. Converting to a string and there it is, the image :wink:

Next tasks:

  • Get the “last” and “upcoming” views to work.
  • Display the standings
  • Let the user choose his/her favorite team and make it visible in the scores and standing tabs.

So, there is a lot to do, I hope I can find enough time next weekend.

1 Like

Very slow progress currently.
Figured out, how to sort the standings. Not very complicated, “just” needed to find the correct syntax.
Next part is: How to sort and eliminate data points. So, the teams are either in “league” NL or AL. They all have a “leagueRank” ( like 1, 2, 3, 4 and so on). That means, two teams will have the same “leagueRank”. I think I need to create a new array, only with teams in the NL league and then sort them by leagueRank. Same goes for the AL league. For me, that sounds easy, but as a beginner, I struggle WHERE I create the new array and where I can sort it, before I display it in a ForEach loop. Oh well, another 8 days to go, only one weekend left. Will figure that out.

Code snippet for sorting by wins and then, if wins are equal, by losses:

do {
let standingsTestData = try decoder.decode([StandingJSONTest].self, from: data)

            let sortedStandingsOverall = standingsTestData.sorted {
                
             // sort by wins; if wins are equal, sort by losses
                ($0.wins, $1.losses) > ($1.wins, $0.losses)
                
            }
            
            // Return the Standings
            return sortedStandingsOverall
        }

1 Like

So, I came to a solution how to filter for the American League and National League for the standings. It’s working, which is good. Though I don’t think that I got the most efficient solution.

In the beginning, I wanted to use a tuple to get the data for the MLB (overall), National League (NL) and American League (AL). I got to the point that I could do the filtering and assigning the values to the arrays, but I got stuck to initialize the data.
So I created three classes, one for MLB, one for AL and one for NL. This data is then displayed in the standingsView. Again, as the code is very similar, I bet there is a solution with less repeating code, but for now I decided to have the code for the MLB, AL and NL and with if statements (which picker is selected), the standings are displayed.

Here is a code snipped for the AL in which I removed all “NL” teams and then sorted by the league rank (which is given by the API/JSON data).

do {
                let standingsTestData = try decoder.decode([StandingJSONTest].self, from: data)
                
                let sortedStandingsLeagueALfiltered = standingsTestData.filter { !$0.league.hasPrefix("NL") }
                 let    sortedStandingsLeagueAL = sortedStandingsLeagueALfiltered.sorted {
                    $0.leagueRank < $1.leagueRank
                }
            
                // Return the Standings
                return (sortedStandingsLeagueAL) 

To have a more consistent look, I added a small “headline” to the overall standing and I put in a similar for the AL and NL (just scroll down to see the NL):

So, the 6 division leagues are also shown. This time 2x filtering (for AL/NL and then for East/Central/West) and sorting for the division rank.

Created some subviews to be able to show all the Z/V/HStacks :wink:

Quite happy with the latest progress.

Open tasks:
Views for last and upcoming games
Indicate user’s favorite team

Afterwards, I might try to clean up the code

2 Likes

It’s done!

The last open tasks are completed, the app is ready :slight_smile:
Have a look into my solution:

1 Like

Great job!! :clap::clap::clap:

1 Like

Thank you :slight_smile:
That was a fantasic challenge, I learned a lot, and I think the result is quite nice :slight_smile:

Hey @Wildsparrow how did you get the logos? I have been trying to use the WikipediaLogoUrl from the API but the SVG format is giving me hell

When I read that some people struggle with the SVG images, I decided to download them from the wikipediateamlogo-url, covert them to PNG, named them as the teamID (for example 1.png), and put them in the assets folder of the app.
As awayTeamID, homeTeamID and teamID is the same, it’s really easy to display them in the scores and standings views.
Strangely, one logo was already linked as png in the wiki-links. Maybe that gives you the trouble?