-1

I am creating a DataSource for my UITableView. Data source is responsible for displaying weather information using WeatherViewModel class. Here is the implementation of WeatherDataSource.

class WeatherDataSource: NSObject, UITableViewDataSource {

    var cellIdentifier: String
    var weatherViewModels = [WeatherViewModel]()

    init(cellIdentifier: String, weatherViewModels: [WeatherViewModel]) {
        self.cellIdentifier = cellIdentifier
        self.weatherViewModels = weatherViewModels
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        print(self.weatherViewModels.count) // This is always empty
        return self.weatherViewModels.count
    }
// some other methods

In my View Controller, I initialize the data source as shown:

 override func viewDidLoad() {

        super.viewDidLoad()
        self.navigationController?.navigationBar.prefersLargeTitles = true

        self.datasource = WeatherDataSource(cellIdentifier: "WeatherCell", weatherViewModels: self.weatherListViewModel.weatherViewModels)

        self.tableView.dataSource = self.datasource
    }

When I add a new item I add it to the weatherViewModels collection in the WeatherListViewModel view model as shown below:

 func addWeatherDidSave(vm: WeatherViewModel) {

        self.weatherListViewModel.addWeatherViewModel(vm)
        print(self.weatherListViewModel.weatherViewModels.count) // this prints 1
        self.tableView.reloadData()
    }

The self.weatherListViewModel.weatherViewModels.count above says 1 which means item got inserted. But when I reload the tableview in line, inside the WeatherDataSource weatherViewModels is still empty! Why is that? When I created the WeatherDataSource I passed the same array to it. Now, I am modifying the array so would it not automatically reflected inside the datasource.

UPDATE: ViewModel Code

class WeatherListViewModel {

    private(set) var weatherViewModels = [WeatherViewModel]()

     func addWeatherViewModel(_ vm: WeatherViewModel) {
        self.weatherViewModels.append(vm)
    }

class WeatherViewModel: Decodable {

    let name: String
    var currentTemperature: TemperatureViewModel

    private enum CodingKeys: String, CodingKey {
        case name
        case currentTemperature = "main"
    }

}

SOLUTION: My solution was to add an update method to the data source and pass in the updated array. I don't really like it but it works.

  func update(weatherViewModels: [WeatherViewModel]) {
        self.weatherViewModels = weatherViewModels
    }

Then in the View Controller you can use it like this:

func addWeatherDidSave(vm: WeatherViewModel) {

 self.weatherListViewModel.addWeatherViewModel(vm)
    self.datasource?.update(weatherViewModels: self.weatherListViewModel.weatherViewModels)
    self.tableView.reloadData()
}
john doe
  • 9,220
  • 23
  • 91
  • 167

3 Answers3

1

Your problem is that you pass self.weatherListViewModel.weatherViewModels instead of self.weatherListViewModel to your WeatherDataSource instance.

Since self.weatherListViewModel.weatherViewModels is an array, it is a value type, not a reference type.

This means that weatherViewModels in your WeatherDataSource instance and weatherViewModels in your WeatherListViewModel instance are different arrays. Although WeatherDataSource is a class, and therefore a reference type, the property itself is a value type. You need to always operate on the array in your WeatherListViewModel instance.

class WeatherDataSource: NSObject, UITableViewDataSource {

    var cellIdentifier: String
    var weatherViewModel: WeatherListViewModel

    init(cellIdentifier: String, weatherViewModel: WeatherListViewModel) {
        self.cellIdentifier = cellIdentifier
        self.weatherViewModel = weatherViewModel
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        print(self.weatherViewModel.weatherViewModels.count) 
        return self.weatherViewModel.weatherViewModels.count
    }
}

ViewController

override func viewDidLoad() {

    super.viewDidLoad()
    self.navigationController?.navigationBar.prefersLargeTitles = true

    self.datasource = WeatherDataSource(cellIdentifier: "WeatherCell", weatherViewModels: self.weatherListViewModel)

    self.tableView.dataSource = self.datasource
}
Paulw11
  • 108,386
  • 14
  • 159
  • 186
  • Thanks! Yes you are correct. For above to work WeatherListViewModel should be a class right? Because I reverted it back to struct and it did not work since struct will make a copy. Class works. – john doe Dec 13 '18 at 19:46
  • Correct, because a struct is a value type, and it will be copies as soon as you mutate it. – Paulw11 Dec 13 '18 at 19:47
0

You're not showing the code for self.weatherListViewModel.addWeatherViewModel(), but if that methods only updates the self.weatherListViewModel array, this change doesn't get propagated to your WeatherDataSource instance, because that's operating on a copy of whatever you've passed in at init time.

Gereon
  • 17,258
  • 4
  • 42
  • 73
  • So even though WeatherViewModel is a class it will still be passed as a copy and not as a reference? – john doe Dec 13 '18 at 18:52
  • Just realized that everything in Swift functions is passed a copy. :( – john doe Dec 13 '18 at 19:03
  • No. Only value types are copied, reference types aren't. Knowing which is which is pretty important :) – Gereon Dec 13 '18 at 19:08
  • Actually when passing arguments to a function they are always copied. According to this post: https://stackoverflow.com/questions/27364117/is-swift-pass-by-value-or-pass-by-reference – john doe Dec 13 '18 at 19:10
  • Since, I am passing an array it is passing the array by copying it instead of referencing the array. It would have been nice if array could be reference based on the contents of the array but looks like it is always pass by value. – john doe Dec 13 '18 at 19:25
0

To change number of rows you need to change self.datasource.weatherViewModels array by adding/removing objects or assign new array to it. Your code does not do that.

kirander
  • 2,202
  • 20
  • 18