Excellent tutorial! However, I am having an issue with the CurrentWeatherView running the final project in XCode 14.3.1 for iOS 16.4 (minimum deployment target iOS 13) on iPhone 14 Max simulator. In the CurrentWeatherViewModel.refresh() method, data is correctly received and correctly mapped to the CurrentWeatherRowViewModel. The problem is that neither of the .sink() method’s callbacks (receiveCompletion or receiveValue) is called and the dataSource property is never set. The view remains fixed with the message “Loading {city}'s weather…” displayed. The URL is correctly formed and returns the correct data when run in an internet browser. No error message is shown in the console.
Has anyone else experienced this issue? Can anyone suggest a remedy?
I am having the same issue using XCode 14.3.1 for iOS 16.4. Sadly, I do not have a remedy.
I am still in the process of gaining experience with SwiftUI, but during debugging, I noticed that the viewModel of the CurrentWeatherView
is being recreated periodically due to a certain behavior of the NavigationLink
that I haven’t fully comprehended yet.
When the button to navigate to the CurrentWeatherView
is clicked, the instance we receive is not the same as the one where the refresh
method was called. The instance where refresh
was invoked isn’t being retained anywhere, causing it to be freshly instantiated. Consequently, the observable also gets reinstantiated. This situation results in the CurrentWeatherViewModel
that is actually being holded by the view not being notified of the data arriving from the API call.
To tackle this issue, I’ve implemented a solution that might not be optimal but serves its purpose. Instead of repeatedly generating instances of CurrentWeatherViewModel
, I maintain one instance within the WeeklyWeatherViewModel
. I recreate this instance only when the city changes. When creating the CurrentWeatherView
, I utilize the CurrentWeatherViewModel
instance stored within the WeeklyWeatherViewModel
.
This is the version I am using that is working.
import SwiftUI
import Combine
class WeeklyWeatherViewModel: ObservableObject, Identifiable {
@Published var city: String = ""
@Published var dataSource: [DailyWeatherRowViewModel] = []
private let weatherFetcher: WeatherFetchable
private var disposables = Set<AnyCancellable>()
private var currentWeatherViewModel: CurrentWeatherViewModel?
init(weatherFetcher: WeatherFetcher,
scheduler: DispatchQueue = DispatchQueue(label: "WeatherViewModel")) {
self.weatherFetcher = weatherFetcher
$city
.dropFirst(1)
.removeDuplicates()
.debounce(for: .seconds(0.5), scheduler: scheduler)
.sink(receiveValue: fetchWeather(forCity:))
.store(in: &disposables)
}
func fetchWeather(forCity city: String) {
self.currentWeatherViewModel = CurrentWeatherViewModel(city: city, weatherFetcher: weatherFetcher)
weatherFetcher.weeklyWeatherForecast(forCity: city)
.map { response in
response.list.map(DailyWeatherRowViewModel.init)
}
.map(Array.removeDuplicates)
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { [weak self] value in
guard let self = self else { return }
switch value {
case .failure:
self.dataSource = []
case .finished:
break
}
},
receiveValue: { [weak self] forecast in
guard let self = self else { return }
self.dataSource = forecast
})
.store(in: &disposables)
}
}
extension WeeklyWeatherViewModel {
var currentWeatherView: some View {
if currentWeatherViewModel == nil {
currentWeatherViewModel = CurrentWeatherViewModel(city: city, weatherFetcher: weatherFetcher)
}
return CurrentWeatherView(viewModel: currentWeatherViewModel!)
}
}
So my issue was different.
When trying to tap “Weather Today”, app was crashed with exec bad access.
In CurrentWeatherView.swfit, line 40, I changed to .onApppear, instead of onAppear{…}.
And no longer having error. No other changes. In case, future readers need help in solving it.
XCode was still showing following errors while tested using XCode 15.2, on physical iPhone 8 with iOS 16.5. But this is not causing app crashing and could be unrelated to the tutorial, so I didn’t resolve it.
**2024-04-21 23:24:39.849613+0700 CombinedWeatherApp[941:119968] Successfully load keyboard extensions**
**2024-04-21 23:24:45.090489+0700 CombinedWeatherApp[941:119968] Metal API Validation Enabled**
**2024-04-21 23:24:45.315882+0700 CombinedWeatherApp[941:119968] [PipelineLibrary] Mapping the pipeline data cache failed, errno 22**