How to manually change the selected UIAction inside UIMenu?
Asked Answered
F

2

9

The Goal: Trying to create a filter selection screen (in a card view) that has UIButtons with UIMenu items attached them. When i select any of the filters, and click on the apply filters button, i dismiss the filter view controller and go back to the collection view screen to refresh the data with the filters that were selected.

The Issue: With the filters already selected, when i want to go back again to change any filters i click on the filters button to open the filters viewcontroller, but obviously none of the UIMenu selections are preserved, as they are reset to the default first index setting.

How To Solve?: When pass my data back from filterVC to the main list screen, i pass the title of the selected UIMenu. I was thinking that i could pass this back into the filterVC and default the menus to the passed in data, (ie. could search for the title and make it the selected UIAction in the UIMenu), however i cant seem to find a way to manually select which UIAction should be the selected state of the UIMenu!

    var spaceTypeMenu: UIMenu {
    return UIMenu(title: "Space Type", image: nil, identifier: nil, options: [], children: spaceTypeMenuItems)
}

    private var spaceTypeMenuItems: [UIAction] {
    return [
        UIAction(title: "All", handler: { (_) in }),
        UIAction(title: "Meeting Room", handler: { (_) in }),
        UIAction(title: "Desk", handler: { (_) in }),
        UIAction(title: "Boardroom", handler: { (_) in })
    ]
}

Then the following is Called from viewdidload()

func createSpaceTypeMenu() {
    spaceTypeButton?.menu = spaceTypeMenu
    spaceTypeButton?.showsMenuAsPrimaryAction = true
}

After the filters have been selected i pass them back to the previous VC:

@IBAction func applyFiltersButtonPressed(_ sender: UIButton) {
    self.dismiss(animated: true) {
        let filter = Filters(site: self.siteButton?.titleLabel?.text,
                             spaceType: self.spaceTypeButton?.titleLabel?.text,
                             date: self.datePickerView.datePickerView?.date,
                             duration: self.selectedDuration)
        self.applyFiltersCallback?(filter)
    }
}

Site, Spacetype, duration are all UIbuttons with a UIMenu for the filter options which are just hardcoded for now as above.

What i want to happen is that if i selected "Desks" before applyFiltersButtonPressed() was called to dismiss the VC, when i come back to this FiltersVC i want "Desks" to still be selected as the text in the UIbutton and also want the tick to be next to "Desks" and dont want the Menu items to default back to the first option which was "All".

Hope that makes sense?

Fourthly answered 2/11, 2021 at 18:54 Comment(0)
F
10

Found out how to do this myself, hopefully it can help out someone else in the same shoes as i was:

private func updateActionState(actionTitle: String? = nil, menu: UIMenu) -> UIMenu {
    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
}

and can be called by:

private func createSiteMenu(actionTitle: String? = nil) {
    let menu = UIMenu(title: "Site", image: nil, identifier: nil, options: [], children: siteMenuItems)
    siteButton?.menu = updateActionState(actionTitle: actionTitle, menu: menu)
    siteButton?.showsMenuAsPrimaryAction = true
}
Fourthly answered 3/11, 2021 at 13:56 Comment(1)
This is great solution, but it seems not to be effective for nested UIMenus. Could you help me with nested Menus?Harrumph
L
1

Thanks, this worked to solve my similar problem. I'm using ios15 popup and pulldown menus but wanted to have the menu populated from an enumeration with rawvalues as the menu options.

enum SortOrder: String, Codable, CaseIterable {
    case name = "Name"
    case category = "Category"
    case qty = "Quantity"
    case value = "Value"
    case token = "Token"
}
let sortClosure = { [self] (action: UIAction) in  
    // closure actions based on self.sortOrderPopup.currentTitle!
}

var sortOrderPopupOptions: [UIAction] = []
for sortOption in ListLibrary.SortOrder.allCases {
    sortOrderPopupOptions.append(UIAction(title: sortOption.rawValue, handler: sortClosure))
}

sortOrderPopup.menu = UIMenu(children: sortOrderPopupOptions)
menuSelection = currentList.sortOrder.rawValue

sortOrderPopup.menu = updateActionState(actionTitle: menuSelection, menu: sortOrderPopup.menu!)

Your solution works perfectly and can be adjusted to modify other properties of the menu!

Loser answered 28/11, 2021 at 20:25 Comment(1)
Please provide the full codeContempt

© 2022 - 2024 — McMap. All rights reserved.