0

I'm very beginner in iOS development, so this problem may look trivial. I searched and could not find any questions about this problem. But I wonder what is correct approach of updating custom cells of a UITableView when you change your data model at run time after initial loading of cells from data model. From change, I mean data entry change, not adding or removing data.

Here is an example. Let's say that I have these DataModel and DataModelCell as follows:

class DataModelView : UITableViewCell {
    @IBOutlet weak var mainLabel: UILabel!
}

class DataModel {
    var title: String = "" {
        didSet {
            // which cell this entry is connected to?
        }
    }
}

...
items: [DataModel] = []

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "DataModelView", for: indexPath)

    cell.mainLabel?.text = items[indexPath.item].title
    // addition line for each approach
    // approach 1:
    // items[indexPath.item].view = cell
    // approach 2:
    // items[indexPath.item].viewIndexPath = indexPath

    return cell
}

My problem is that when I changed title of one of cells in my data model at run time, I like to update corresponding cell in UI. I like to know what is the best approach to make a relationship between data model'd entry and cell in UITableView.

There are 3 different approaches that comes to my mind. I want to know if they are correct or not or if there is any better method:

1st approach: a weak pointer to cell in my data entry like this:

class DataModel {
    weak var view: DataModelView?
    var title: String = "" {
        didSet {
            view?.mainLabel?.text = title
        }
    }
}

2nd approach: keep IndexPath of cell in my data entry like this:

class DataModel {
    var viewIndexPath: IndexPath?
    var title: String = "" {
        didSet {
            // call delegate to controller and ask to update cell for viewIndexPath
        }
    }
}

3rd approach: don't keep anything which corresponds to cell in data entry:

class DataModel {
    var title: String = "" {
        didSet {
            // call delegate to controller and ask to find and update cell for self
        }
    }
}

In first 2 approaches, I keep relationship between cell and data model in my data model. In 3rd approach, I need to keep this relationship in controller.

Are all these approaches correct(especially first one)? Which one do you suggest? and What is best approach generally?

I have seen that people keep a pointer to data model in their view in order to update data model from view. I wonder if it is correct in the other way too or not(1st approach).

Afshin
  • 8,839
  • 1
  • 18
  • 53
  • please show us `cellForRowAtIndexPath` implementation – andesta.erfan Dec 28 '18 at 11:07
  • How you can load TableView from the other class...... every approach is wrong. – dahiya_boy Dec 28 '18 at 11:21
  • @andesta.erfan I added `cellForRowAtIndexPath`, but it just a default and simple implementation to load initial items. I don't have any problem here. I like to see updates after this initial load. – Afshin Dec 28 '18 at 11:21
  • @dahiya_boy each data entry is connected to 1 cell. and I want to update corresponding cell when I update its data. – Afshin Dec 28 '18 at 11:22
  • Something like the 3rd approach. Your view controller should be notified of updates in the view model and it can then reload the appropriate cells – Paulw11 Dec 28 '18 at 11:26
  • @Paulw11 do I need to reload whole cell? will using a weak pointer to cell be a bad idea? – Afshin Dec 28 '18 at 11:29
  • Any close coupling between your data model and a specific view is a bad idea. Whether you reload the cell or simply get a reference to the cell (if it is visible) and update the specific text field is up to you. The point is that it is the view controller that links the data model to the view. – Paulw11 Dec 28 '18 at 11:31
  • @Afshin Can you update us, how your model is changing? Like data is coming from server or there is TextField in cell and you updating TF text into model????? – dahiya_boy Dec 28 '18 at 11:34
  • @dahiya_boy it is more like 2nd one. my whole number of cells is mostly fixed. but each cell is dynamically updates based on changes of data for that cell. – Afshin Dec 28 '18 at 11:37
  • @Afshin Yeah, 2nd approach probably to work. – dahiya_boy Dec 28 '18 at 11:53

2 Answers2

1

4th approach: a weak pointer to data model in the cell and key value observing

class DataModelView : UITableViewCell {
   @IBOutlet weak var mainLabel: UILabel!

   var observation : NSKeyValueObservation?

   weak var model : DataModel? {
       didSet {
           if model == nil { observation = nil }
           else {
               observation = model!.observe(\.title, options: [.new])  { [weak self] (model, _) in
                  self?.mainLabel.text = model.title
               }
           }
       }
    }
}

KVO requires inheritance from NSObject

class DataModel : NSObject {
    @objc dynamic var title: String = ""
}

In cellForRow pass the model

let cell = tableView.dequeueReusableCell(withIdentifier: "DataModelView", for: indexPath) as! DataModelView
cell.model = items[indexPath.row]

and remove the observer in didEndDisplaying

func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    (cell as! DataModelView).observation = nil
}
vadian
  • 274,689
  • 30
  • 353
  • 361
  • This approach looks great. I think the only drawback is if you have multiple field you like to update or not. if for example you want to update 5-6 fields for cell, you will install a lot of observers which will make code a little messed up. Anyway, thanks for this approach. – Afshin Dec 28 '18 at 12:10
-1

It should be like this

class DataModelView: UITableViewCell {
    @IBOutlet weak var mainLabel: UILabel!
    var yourModel: DataModel? {
       didSet {
         mainLabel.text = yourModel?.title
       }
}

And in your cellForRowAt

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DataModelView", for: indexPath)

cell.yourData = items[indexPath.item]
return cell
}

After any item changed in your items array just call

tableView.reloadData()
Emre Önder
  • 2,408
  • 2
  • 23
  • 73