0

I know how to preserve the action we have done on UITableView, after scrolling back and forth.

Now Iam doing a simple UITableView on MVVM which has a Follow button .follow button like this. Follow button changes to Unfollow after click and resets after scrolling. Where and How to add the code to prevent this?

Here is the tableview Code

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return Vm.personFollowingTableViewViewModel.count
    
}
var selectedIndexArray:[Int] = []
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    guard let cell = tableView.dequeueReusableCell(withIdentifier: FollowList_MVVM.PersonFollowingTableViewCell.identifier , for: indexPath) as? PersonFollowingTableViewCell else{
        return UITableViewCell()
    }


    cell.configure(with: Vm.personFollowingTableViewViewModel[indexPath.row])
    cell.delegate = self
    return cell
    
    
}

and configure(with: ) function

@objc public func didTapButton(){
    let defaultPerson = Person(name: "default", username: "default", currentFollowing: true, image: nil)
    let currentFollowing = !(person?.currentFollowing ?? false)
    person?.currentFollowing = currentFollowing
    delegate?.PersonFollowingTableViewCell(self, didTapWith: person ?? defaultPerson )    
    configure(with: person ?? defaultPerson)
}


func configure(with person1 : Person){
    
    
    self.person = person1
    nameLabel.text = person1.name
    usernameLabel.text = person1.username
    userImageview.image = person1.image
    
    
    
    if person1.currentFollowing{
        //Code to change button UI
        
    }

custom delegate of type Person is used

Ae Ri
  • 185
  • 1
  • 12
  • 3
    If you are really using MVVM then your view model should hold your model objects (Person) and not your cells – Joakim Danielson Jun 13 '21 at 08:42
  • 1
    @JoakimDanielson Could you please elaborate? in ViewModel I only have have some hardcoded data based on models – Ae Ri Jun 13 '21 at 08:52
  • You need to at least tell the view model when you have updated a person, it is the view model that holds the data, not the cell. Your Person object in the cell should only be seen as a copy of the original data which resides, as mentioned, in the view model. – Joakim Danielson Jun 13 '21 at 08:56
  • 1
    @JoakimDanielson Thanks. I have done more reference on this and updated with my own answer – Ae Ri Jun 16 '21 at 18:41

2 Answers2

1

I guess your main issue is with Button title getting changed on scroll, so i am posting a solution for that.

Note-: Below code doesn’t follow MVVM.

Controller-:

import UIKit

class TestController: UIViewController {
    
    @IBOutlet weak var testTableView: UITableView!
    var model:[Model] = []
    
    override func viewDidLoad() {
        for i in 0..<70{
            let modelObject = Model(name: "A\(i)", "Follow")
            model.append(modelObject)
        }
    }
}

extension TestController:UITableViewDelegate,UITableViewDataSource{
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        return model.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TestTableCell
        cell.dataModel = model[indexPath.row]
        cell.delegate = self
        return cell
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 100
    }
    
}


extension TestController:Actions{
    func followButton(cell: UITableViewCell) {
        let indexPath = testTableView.indexPath(for: cell)
        model[indexPath!.row].buttonTitle = "Unfollow"
        testTableView.reloadRows(at: [indexPath!], with: .automatic)
    }
}

class Model{
    var name: String?
    var buttonTitle: String
    
    init(name: String?,_ buttonTitle:String) {
        self.name = name
        self.buttonTitle = buttonTitle
    }
}

Cell-:

import UIKit

protocol Actions:AnyObject{
    func followButton(cell:UITableViewCell)
}

class TestTableCell: UITableViewCell {
    
    @IBOutlet weak var followButtonLabel: UIButton!
    @IBOutlet weak var eventLabel: UILabel!
    
    var dataModel:Model?{
        didSet{
            guard let model = dataModel else{
                return
            }
            
            followButtonLabel.setTitle(model.buttonTitle, for: .normal)
            eventLabel.text = model.name
        }
    }
    
    weak var delegate:Actions?
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }
    
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        
        // Configure the view for the selected state
    }
    
    @IBAction func followAction(_ sender: Any) {
        delegate?.followButton(cell:self)
    }
}

To convert this into MVVM approach, there are few things you need to change and move out.

  1. The loop I have in viewDidLoad shouldn’t be there. That will be some API call, and should be handled by viewModel, and viewModel can delegate that to other repository to handle or handle itself. Upon receiving response viewModel update its state and communicate with View (in our case tableView) to re-render itself.

  2. Code in extension where I am updating model object shouldn’t be in controller (model[indexPath!.row].buttonTitle = "Unfollow"), that has to be done by viewModel, and once the viewModel state changes it should communicate with view to re-render.

  3. The interaction responder (Button action) in Cell class, should delegate action to viewModel and not controller.

  4. Model class should be in its own separate file.

In short viewModel handles the State of your View and it should be the one watching your model for updates, and upon change it should ask View to re-render.

There are more things you could do to follow strict MVVM approach and make your code more loosely coupled and testable. Above points might not be 100% correct I have just shared some basic ideas i have. You can check article online for further follow up.

Tushar Sharma
  • 2,839
  • 1
  • 16
  • 38
0

The above answer works . But I have gone through what suggested by @Joakim Danielson to find what exactly happens when you are updating the View and Why it is not updating on ViewModel

So I made an update to delegate function

  1. ViewController delegate function

    func PersonFollowingTableViewCell1( _ cell: PersonFollowingTableViewCell, array : Person, tag : Int)

Here, I called the array in the Viewmodel and assigned the values of array in func argument to it.

like ViewModel().Vmarray[tag].currentFollow = array[tag].currentFollow

Ae Ri
  • 185
  • 1
  • 12