iosswiftxcodeuimenuuiaction

How to manually change the selected UIAction inside UIMenu?


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?


Solution

  • 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
    }