I did find a way to optimize the Combine for doing city lookups! It’s much better now and more efficient as the Combine operator I use now will cancel all previous requests to lookup cities if it’s still processing.
Got everything connected so when you select a city you looked up, it’ll update your list of cities.
Trouble
Ran into trouble with the main UI. The problem is I didn’t plan it out well enough. I didn’t have a clear idea of exactly how I was going to organize all the cities, view a city’s weather, and delete a city.
I also ran into a possible Xcode bug with Combine but it’ll take more work to verify it with a sample project that a friend of mine at Apple wants me to create. Ugh. Do I really want to take the time to do this?
Yeah, the Combine issue was fixed. It was actually an error on my part by decoding into the wrong object. The problem was Xcode couldn’t tell me that. It would just show this weird error when trying to compile.
Why the GeometryReader?
Great question. That image is larger than the size of that device. And when it’s larger it pushes out the containing ZStack.
This will mess up the other views.
The GeometryReader is kind of a hack to prevent the image from pushing out the ZStack.
Now I’m not saying this is the best way but at this time I couldn’t find a better way.
Oh wow, that is very interesting.
Would adding a frame of UIScreen.main.bounds to the image do the same thing?
If yes I think that’s a cleaner architecture, the GeometryReader looks really hacky to me haha.
I learned quite a bit yesterday and feel I worked on this entirely too long because of so many problems I ran into.
Some of the problems I had to figure out while working with a Paging TabView:
Problem
When adding a new city, I was trying to navigate to the new city BEFORE the API finished returning the weather data. This caused an “index out of bounds” error.
Solution
Kept user on the Add City screen until the API finished returning the weather data.
I wanted to navigate to the new city I just added. City weather is inside the Paging TabView. So how do I do this?
Solution
I had to add a selection @State property that I could change programmatically that is bound to the TabView. I change this property when the city lookup sheet is closed.
@State private var citySelection: Int = 0
… TabView(selection: $citySelection) {
Problem
Deletion proved a challenge. The actual deleting was fine. It was updating the UI. Originally, I would remove the item from the array and depend on the UI to update automatically. Which it did. But then crashed.
Apparently, the TabView is implementing a UICollectionView behind the scenes and I’d get an NSInternalInconsistencyException.
Someone suggested I add a.id(UUID()) to my TabView. While this did work, the app crashed again when swiping.
I think the problem was a new UUID() was getting created if the view refreshed. This caused the TabView to recreate again when swiped too. So I set the id to a stable value that ONLY changed after deleting. Causing the TabView to be refreshed (recreated).
Was this the best answer? Probably not. I think this is a bug. But with a new version of Xcode coming I’m not going to file a Feedback until I try the new version to see if it’s already been fixed.
Problem
Originally I had the side-scrolling hourly temperatures below the TabView. And then when I swiped in the TabView, I detected the selection changed and updated the hourly temperature values.
This was very error-prone once I got into adding, deleting, and refreshing the TabView.
Solution
I moved the hourly temperatures INSIDE the TabView to keep the values synced to the city.
I want to change the UI around a little though. Maybe later.
More to do
The deletion of cities will take a little more testing to make sure it’s solid.
I need a Settings view to change from Farenheit to Celcius. The logic is there, I just need a place to change it.
If I deleted a city and then added a new one, the index got messed up within the TabView. I reorder the index values after deleting a city. This helps with programmatic navigation.
Added progress view when first loading.
Added Settings view to switch between Fahrenheit and Celcius.
Added app icon.
Clean up code, added comments on where to find more info on the Combine parts in my book.
Tweaked UI, adjusted some padding, added background to hourly temps.
Thank you for your support, Ralph! If you look in the code comments you’ll find references to specific parts of the book. Should get you pointed in the right direction to learn more in the book examples.
Found the API endpoint for scores by date (to get yesterday’s data)
Found the API endpoint for schedules (it’s on the same page, so that’s handy)
Team Images
Looks like there’s a different endpoint called “Teams (All)” to get all the team images and color palettes?
Could be interesting to use these colors in the UI.
Problems
Thinking about how to cache the team info so I only need to get it one time. Thinking Core Data is a good solution, but man, I haven’t used Core Data for anything at work so I never really used it. Not sure how much time I want to invest in learning it.
Anything I start to learn usually turns into a separate reference product which is very time-consuming. So first, I’m going to research quicker solutions and maybe have to learn Core Data after. This will be a good challenge for sure!
Man, I hate to say this but I think I’m going to have to throw in the towel on this challenge!
There is a lot of exploratory research I want to do to make this work such as:
Explore using the NSCache object
Explore persisting with a file
Explore persisting using Core Data
I think these topics could be a lot of fun to play with but then I look at other priorities on my plate right now:
My plan was to call the Teams (All) API endpoint and store all the team data locally. This API gets me this data:
struct TeamDO: Decodable, Identifiable {
let id: Int
var city = ""
var name = ""
var primaryColor = ""
var secondaryColor = ""
var tertiaryColor = ""
var imageUrl = ""
enum CodingKeys: String, CodingKey {
case id = "TeamID"
case city = "City"
case name = "Name"
case primaryColor = "PrimaryColor"
case secondaryColor = "SecondaryColor"
case tertiaryColor = "TertiaryColor"
case imageUrl = "WikipediaLogoUrl"
}
}
So I wanted to persist them and then when I got score info, I could access this local data and get the full team names, logos, colors.
Well, good luck to everyone else on the challenge!