Kevvv
Kevvv

Reputation: 4023

How to update a table view cell using a delegate

I have a delegate method that passes an object from a detail view controller to a master view controller, which is a table view controller. The object is then used to update data for the table view:

var filters = [Filter]()
func didSelectFilter(selectedFilter: Filter) {
    // finds the relevant data from the array and updates it
    for case var filter in filters where filter.title == selectedFilter.title {
        filter.setting = selectedFilter.setting
    }
    tableview.reloadData()
}

I'm able to confirm that the object is being passed properly. The object is as follows:

struct Filter {
    let title: FilterType
    var setting: String
}

However, the updated data is not being reflected on the table view.

I've tried assigning each data with an index path and updating the specific cell, but still doesn't reflect the change:

if let indexPath = filter.indexPath {
    let foundIndexPath = IndexPath(row: indexPath.row, section: indexPath.section)
    if let cell = tableView.cellForRow(at: foundIndexPath) {
        cell.textLabel?.text = filter.setting
    }
}

How I use the delegate method in the detail view controller:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let selectedData = parsedData[indexPath.row]
    // update the data to be passed
    filter.setting = selectedData
    // pass the data to the master view controller
    delegate?.didSelectFilter(selectedFilter: filter)
    self.navigationController?.popViewController(animated: true)
}
class Filter {
    var title: FilterType!
    var setting: String!

    init(title: FilterType, setting: String) {
        self.title = title
        self.setting = setting
    }
}

In other words, the Filter object is created in the master view controller, passed to the detail view controller, gets modified, and then passed back to the master view controller. The problem is that the change doesn't get reflected in the master view controller's table view.

Upvotes: 0

Views: 559

Answers (2)

DonMag
DonMag

Reputation: 77442

There are various ways to do this - and you can work on it using just your data first, then implement it in your multiple controllers.

Assuming you have something like this:

enum FilterType {
    case TypeA, TypeB, TypeC, TypeD
}
struct Filter {
    let title: FilterType
    var setting: String
}

Here's an example you can run as a simple view controller:

class MasterViewController: UIViewController {
    
    var filters: [Filter] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // init array with 7 Filter objects, various "title" types
        filters.append(Filter(title: .TypeA, setting: "1"))
        filters.append(Filter(title: .TypeB, setting: "2"))
        filters.append(Filter(title: .TypeC, setting: "3"))
        filters.append(Filter(title: .TypeD, setting: "4"))
        filters.append(Filter(title: .TypeB, setting: "5"))
        filters.append(Filter(title: .TypeC, setting: "6"))
        filters.append(Filter(title: .TypeB, setting: "7"))

        print("array before update")

        filters.forEach {
            print($0)
        }

        print() // blank line

        // create "selected" Filter - this would come from your Detail VC
        let selFilter = Filter(title: .TypeB, setting: "Change Me")
        
        didSelectFilter(selectedFilter: selFilter)
        
        print("array after update")
        
        filters.forEach {
            print($0)
        }
        
        print() // blank line

    }
    
    func didSelectFilter(selectedFilter: Filter) {

        // change .setting on each array element where .title == selectedFilter.title
        
        filters.indices.filter { filters[$0].title == selectedFilter.title } .forEach {
            filters[$0].setting = selectedFilter.setting
        }

        // tableView.reloadData()
    }
    
}

The debug console output will look like this:

before
Filter(title: TestApp.FilterType.TypeA, setting: "1")
Filter(title: TestApp.FilterType.TypeB, setting: "2")
Filter(title: TestApp.FilterType.TypeC, setting: "3")
Filter(title: TestApp.FilterType.TypeD, setting: "4")
Filter(title: TestApp.FilterType.TypeB, setting: "5")
Filter(title: TestApp.FilterType.TypeC, setting: "6")
Filter(title: PanZoom.FilterType.TypeB, setting: "7")

after
Filter(title: TestApp.FilterType.TypeA, setting: "1")
Filter(title: TestApp.FilterType.TypeB, setting: "Change Me")
Filter(title: TestApp.FilterType.TypeC, setting: "3")
Filter(title: TestApp.FilterType.TypeD, setting: "4")
Filter(title: TestApp.FilterType.TypeB, setting: "Change Me")
Filter(title: TestApp.FilterType.TypeC, setting: "6")
Filter(title: PanZoom.FilterType.TypeB, setting: "Change Me")

As you can see, the elements where .title == .TypeB had the .setting property updated.

If your know your array will have only one element of each "title" type, instead of getting the indices of all elements with that type, you could do:

    if let idx = filters.firstIndex(where: { $0.title == selectedFilter.title }) {
        filters[idx].setting = selectedFilter.setting
    }
    

Upvotes: 0

Eilon
Eilon

Reputation: 2982

Since your model is a struct, i.e. value type, when creating the variable in for case var filter... you are essentially creating a copy and not modifying the data source it self.
What you can do is either turn your model into a class so you are just creating a reference to the object in the data source or replace the object in the index path. For example:

for case var filter in filters where filter.title == selectedFilter.title {
    filter.setting = selectedFilter.setting
    self.filters[theRelevantIndexPosition] = filter
}

Upvotes: 1

Related Questions