Group Group Group Group Group Group Group Group Group

MVVM with Combine Tutorial for iOS | raywenderlich.com

Thank you very much for this. It is extremely helpful. The one thing I am still uncomfortable with is having to import SwiftUI into the view models. It seems like view models shouldn’t need to know about SwiftUI. But I don’t see any obvious way around that. Does anyone have any thoughts on this?

If anyone is interested, I have started an app using your tutorial as a guideline. I have also added CoreData and a lot of tests. It is still a work in progress but I would appreciate any feedback. Thanks. It is here: https://github.com/jodihum/needledrop

While I appreciate the work that went into this tutorial, I feel like it completely loses its purpose. It’s far too overly complicated for something that sounded like it was going to be a tutorial on how to work with Combine and MVVM.

Starting small then building up to the more advanced features in my opinion would be a much better approach. This tutorial feels like it “starts on step 27” so to speak.

4 Likes

+1
it indeed loses its focus and purpose

There is an issue with the current code in the WeeklyWeatherViewModel.
It seems that if you keep a reference to the subscription generated in the init method for `$city, it works as expected… something like:

  // reference! 
  var cancellable:AnyCancellable? 

  // 1
  init(
    weatherFetcher: WeatherFetchable,
    scheduler: DispatchQueue = DispatchQueue(label: "WeatherViewModel")
  ) {
    self.weatherFetcher = weatherFetcher
   
    // 2
    cancellable = $city
      .dropFirst(1)
2 Likes

Is the use of weak needed? Since the life cycle of the Cancellable is dependent on the view model life cycle using the store(in:) call at the end?

This works great! Thank you!

I think there is a problem with the WeeklyWeatherViewModel as it doesn’t capture the changes of the @Published var city

The result of the .sink needs to be stored instead of discarding the return value _ = $city

    init(weatherFetcher: WeatherFetchable, scheduler: DispatchQueue = DispatchQueue(label: "WeatherViewModel")) {
        self.weatherFetcher = weatherFetcher
        $city
            .dropFirst()
            .debounce(for: .seconds(0.5), scheduler: scheduler)
            .sink(receiveValue: fetchWeather(forCity:))
             // this is missing
            .store(in: &disposables)
    }
1 Like

@rperes Do you have any feedback about this? Thank you - much appreciated! :]

Neither the starter (tutorial completed) nor the final project return any weather data for me.

I can confirm that the subscriber needs to be stored for the app to work. In fact I was confused that why is it even working, why is the city’s subscriber not getting deallocated. Looks like there was some issue in Xcode 11.1. The app works fine in Xcode 11.1 but we don’t see any results in Xcode 11.3.1 because subscriber is getting deallocated, which is expected, unless I am missing something.

I never saw an awkward and failed tutorial like this on this web site.

Also, Sample project is not working. !!!

The problem has been pointed out. The AnyCancellable for the city publisher needs to be retained. The subscription to the $city publisher exists only within the scope it was created in. i.e. the init method. you need private var citySubscription: AnyCancellable? below the the disposables var in the WeeklyWeatherViewModel. and then in the init change _ = $city… to citySubscription = $city

I am sorry if this is a very amateur comment, but I’ve been trying to type code by code to learn based on the starter project, and I stumble upon a roadblock and I’ve been really troubled by it.
Yes I could’ve just copy and pasted but I really want to learn how to type the return session.dataTaskPublisher snippet in " Building the App" section for forecast().

Firstly, there is a compilation error when I was typing mapError where it won’t autocomplete .network. May I know why is that, in that case if I am doing this on my own how would i think of using .network().

Secondly, the autocompletion for flatMap is
(maxPublishers: <#T##Subscribers.Demand#>, <#T##transform: ((data: Data, response: URLResponse)) -> Publisher##((data: Data, response: URLResponse)) -> Publisher#>)
However, in the example, in the closure block there is only one variable declared being pair, how does that work?

Also, is there any way to let XCode escape to
.flatMap(maxPublishers: .max(1)) { pair in
decode(pair.data)
}
when using tab autocompletion will just bring me to the 2nd variable (<#(data: Data, response: URLResponse)#>) -> Publisher in inside the function.

Sorry for the poorly worded and phrased question as well, but I will really appreciate this reply

I’m about a week into Swift, Use to be a webdev (Vue, React etc…)

Seeing this Tutorial, I really get that Swift UI is just a swift version of react. Seems like it would have better performance though …

Yeah, the code as-is will not work because it needs to store the cancelables.

Could someone at RW please update the init() code with .store(in: &disposables)?

I was losing my mind on this for some time until I realized that RW has a comments section. I kept thinking I missed some critical line of code.

Since this is one of the first post-hello-world tutorials that comes on Google for terms involving “swiftui”, I have to wonder how many other people like me are running into the same issue

OMG, thanks for this. You saved me significant time. One would assume that there is a process at RW where authors always ensure tutorial projects are still working whenever a new stable version of Xcode is released (especially in the case of paying tutorials). I downloaded this tutorial’s resources only today and the bug is still there.

There are more serious bug in this app.
If you try such city as Moscow, Murmansk, Basel, Clyde River or Sisimiut ( where snow), you won’t see anything.
Because of incomplete enumMainEnum:

enum MainEnum: String, Codable {
case clear = "Clear"
case clouds = "Clouds"
case rain = "Rain"
}

You need something like this:

enum MainEnum: String, Codable {
case clear = "Clear"
case clouds = "Clouds"
case rain = "Rain"

case snow = "Snow"
case drizzle = "Drizzle"
case thunderstorm = "ThunderStorm"
case mist = "Mist"
case smoke = "Smoke"
case haze = "Haze"
case dust = "Dust"
case fog = "Fog"
case sand = "Sand"
case ash = "Ash"
case squall = "Squall"
case tornado = "Tornado"
}

Or after some time you suddenly stop receiving information.
Probably, it’s a bad idea to use enum in Codable Model:

struct Weather: Codable {
    let main: MainEnum
   let weatherDescription: String
}

Better to use String?

"weather":[{"id":600,"main":"Snow","description":"light snow","icon":"13n"}]
"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}]
"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}]

And YES, buggy tutorial’s resources are still here.

@rperes Can you please help with this when you get a chance? Thank you - much appreciated! :]

There is also a leak in the project (or perhaps SwiftUI ), multiple instances of CurrentWeatherViewModel are created.

@rperes Can you please help with this when you get a chance? Thank you - much appreciated! :]