Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change the state for an UIAction inside UIMenu at runtime?

Tags:

ios

uikit

swift

How to change the state for an UIAction? The goal is to toggle a state checkmark next to an UIAction inside UIMenu.

enter image description here

Changing a UIAction's state via a reference stored in the view controller does not seem to change the state at all. Am I missing anything?

// View Controller
internal var menuAction: UIAction!

private func generatePullDownMenu() -> UIMenu {
    menuAction = UIAction(
        title: "Foo",
        image: UIImage(systemName: "chevron.down"),
        identifier: UIAction.Identifier("come.sample.action"),
        state:  .on
    ) { _ in self.menuAction.state = .off } // <--- THIS LINE


    let menu = UIMenu(
        title: "Sample Menu",
        image: nil,
        identifier: UIMenu.Identifier("com.sample.menu"),
        options: [],
        children: [menuAction]
    )

    return menu
}

// Inside UI setup code block
let buttonItem = UIBarButtonItem(
    title: "",
    image: UIImage(systemName: "chevron.down"),
    primaryAction: nil,
    menu: generatePullDownMenu()
)

Tried to change the action state from the closure directly and got the "Action is immutable because it is a child of a menu" error. Now I suspect an action object is always an immutable object.

menuAction = UIAction(
    title: "Foo",
    image: UIImage(systemName: "chevron.down"),
    identifier: UIAction.Identifier("come.sample.action"),
    state:  .on
) { action in action.state = .off } // <--- THIS LINE
like image 414
X.Creates Avatar asked Dec 11 '22 00:12

X.Creates


2 Answers

Replace the entire UIMenu object on state change would do the trick.

// view controller
internal var barButton: UIBarButtonItem!

// UI setup function
barButton = UIBarButtonItem(
    image: UIImage(systemName: "arrow.up.arrow.down.square"),
    primaryAction: nil,
    menu: generatePullDownMenu()
)

// On state change inside UIAction 
let actionNextSeen = UIAction(
    title: "foo",
    image: UIImage(systemName: "hourglass", )
    state: someVariable ? .off : .on
) { _ in
    someVariable = false
    self.barButton.menu = self.generatePullDownMenu()
}

REFERENCE

https://developer.apple.com/forums/thread/653862

like image 167
X.Creates Avatar answered May 30 '23 19:05

X.Creates


You will need to recreate the Menu. This example will also correctly select the item that was tapped on:

private func setupViews()
    timeFrameButton = UIBarButtonItem(
        image: UIImage(systemName: "calendar"),
        menu: createMenu()
    )
    navigationItem.leftBarButtonItem = timeFrameButton
}

private func createMenu(actionTitle: String? = nil) -> UIMenu {
    let menu = UIMenu(title: "Menu", children: [
        UIAction(title: "Yesterday") { [unowned self] action in
            self.timeFrameButton.menu = createMenu(actionTitle: action.title)
        },
        UIAction(title: "Last week") { [unowned self] action in
            self.timeFrameButton.menu = createMenu(actionTitle: action.title)
        },
        UIAction(title: "Last month") { [unowned self] action in
            self.timeFrameButton.menu = createMenu(actionTitle: action.title)
        }
    ])
    
    if let actionTitle = actionTitle {
        menu.children.forEach { action in
            guard let action = action as? UIAction else {
                return
            }
            if action.title == actionTitle {
                action.state = .on
            }
        }
    } else {
        let action = menu.children.first as? UIAction
        action?.state = .on
    }
    
    return menu
}
like image 20
José Avatar answered May 30 '23 19:05

José