How to update values in an already presented context menu?

Viewed 992

I'm following this tutorial https://kylebashour.com/posts/context-menu-guide and am trying to repurpose this snippet which presents a context menu:

class TableViewController: UITableViewController {
    let data: [MyModel] = []

    override func viewDidLoad() {
        super.viewDidLoad()

        // Configure the table view
    }

    override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
        let item = data[indexPath.row]

        return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { suggestedActions in

            // Create an action for sharing
            let share = UIAction(title: "Share", image: UIImage(systemName: "square.and.arrow.up")) { action in
                print("Sharing \(item)")
            }

            // Create other actions...

            return UIMenu(title: "", children: [share, rename, delete])
        }
    }
}

5 seconds after the context menu is presented I would like to update the title of the menu

UIMenu(title: "expired", children: [share, rename, delete])

and ensure its children have the .disabled attributed set.

UIAction(title: "Share", image: UIImage(systemName: "square.and.arrow.up", , attributes: .disabled)

Is there a way I can I update the already presented context menu's title, and the attributes of its children?

1 Answers

If you want dynamics table you should work with data models and update them every time you want to change the data showed on the table view.

What I mean? A good approach is to have for every row in the table view a model with the data that you want to show in that row (CellModel).

So, in your table view controller you are going to have a list of CellModels:

private var cellModels = [ CellModel ]()

Create cell models on your view did load function and implemente UITableViewDatasource functions:

// MARK: - UITableViewDatasource implementation.
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return self.cellModels.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cellModel = self.cellModels[indexPath.row]

    let cell = tableView.dequeueReusableCell(withIdentifier: kTableViewCellIdentifier, for: indexPath)
    cell.textLabel?.text = cellModel.cellData.menuDataTitle

    return cell
}

Now It's time to work with the CellModel class: this class represents the data that you are going to show in one row. In every row of this simple tutorial you are showing a string with a title, but imagine that you want to show a title, subtitle and an image, in this case It's convenient to create a class in order to represents the data that you are going to show on the row. I named It as "CellData":

class CellData {
    // MARK: - Vars.
    private(set) var menuDataTitle: String

    // MARK: - Initialization.
    init(title: String) {
        self.menuDataTitle = title
    }
 }

On the other hand for each row you have a menu to show, so It's convenient to have a model that represents that menu that you are going to show. I named It as "CellContextMenuData":

class CellContextMenuData {
    // MARK: - Vars.
    private(set) var contextMenuTitle: String
    private(set) var contextMenuActions = [ UIAction ]()

    // MARK: - Initialization.
    init(title: String) {
        self.contextMenuTitle = title
    }
}

This model have a title and actions to show on the menus:

// MARK: - UITableViewDelegate.
override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
    let cellModel = self.cellModels[indexPath.row]
    let cellContextMenuData = cellModel.cellContextMenuData

    return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { suggestedActions in
        return UIMenu(title: cellContextMenuData.contextMenuTitle, children: cellContextMenuData.contextMenuActions)
    }
}

So, every time that you want to change the menu title (or Actions):

1 - Retrieve the cell model that represents the row that you want to change.

2 - Change the title of the CellContextMenuData.

3 - Reload the tableview.

Example:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    // Timer to change our data.
    Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false) { [weak self] timer in
        let cellModel = self?.cellModels[3]
        let cellContextMenuData = cellModel?.cellContextMenuData

        cellContextMenuData?.contextMenuUpdate(title: "Expired")
        cellContextMenuData?.contextMenuSetActionsShareUpdateDelete()

        self?.tableView.reloadData()
    }
}

I created an example, feel free to use/see it: https://bitbucket.org/gastonmontes/contextmenuexample

Related