Search code examples
swiftuitableviewios13uimenuuiaction

Does iOS 13 - UIMenu have a bug that doesn't show its image?


Paste the following code into a project:

No image shows next to 'Device Honey' ie the UIMenu However the image shows up next to 'Copy' ie the UIACtion.

Am I doing something wrong? If this is a bug? Is there a workaround?

class ViewController: UIViewController {
    let tableview: UITableView = {
        let tv = UITableView()
        tv.frame = UIScreen.main.bounds

        return tv
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(tableview)
        tableview.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        tableview.delegate = self
        tableview.dataSource = self
    }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        if cell.detailTextLabel == nil {
            cell = UITableViewCell(style: .value1, reuseIdentifier: "cell")
        }
        cell.textLabel?.text = "Honey"
        cell.detailTextLabel?.text = "iOS developer"

        return cell
    }

    @available(iOS 13.0, *)
    func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {

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

            return self.makeContextMenu(for: indexPath)
        })
    }

    @available(iOS 13.0, *)
    func makeContextMenu(for indexPath: IndexPath) -> UIMenu? {

        let copyAction = UIAction(title: "Copy", image: UIImage(systemName: "square.and.arrow.up")) { [weak self] _ in
            guard let self = self else { return }
            let cell = self.tableview.cellForRow(at: indexPath)
            let pasteboard = UIPasteboard.general
            pasteboard.string = cell?.detailTextLabel?.text
        }

        guard let cell = self.tableview.cellForRow(at: indexPath), let title = cell.textLabel?.text else { return nil}
        return UIMenu(title: "Device \(title) ", image: UIImage(systemName: "square.and.arrow.up"), children: [copyAction])
    }
}

enter image description here

In Apple's WWDC demo they're able to do it as shown below:

enter image description here


Solution

  • A context menu has two parts: the preview and the menu. Both are optional. The "groceries" thing in the Apple screenshot is the preview, not the menu. By default, the cell is snapshotted and the snapshot is displayed as the preview. The menu itself in the Apple screenshot has no image and no title. And that's what you should do too! The last line

    return UIMenu(...
    

    ...should have no title and no image. This menu, the one that wraps everything else and is returned, is the top-level menu, and it is displayed differently (as your own screenshot shows). It looks best without a title, and it cannot display an image at all. Its job is to wrap everything else and to provide an identifier, and that's all.

    You will then get something like this:

    enter image description here