Use action with selector in UIAction for menus in UIKit - swift

Im trying to use action in the UIAction for my menu item in UIKit. So, For first button Im not able to apply action. It shows me error "Cannot find 'action' in scope"
I really want to use selector in this case. I want to know what's perfect way to take action on the selector
class MessageViewController : UIViewController, UITableViewDelegate {
private lazy var first = UIAction(title: "Edit", image: UIImage(systemName: "pencil.circle"), attributes: [], state: .off) { [self]_ in
action: #selector(self.RightSideBarButtonItemTapped(_:))
}
private lazy var second = UIAction(title: "Second", image: UIImage(systemName: "pencil.circle"), attributes: [.destructive], state: .on) { action in
print("Second")
#selector(self.sendMessageRightSideBarButtonItemTapped(_:))
}
private lazy var third = UIAction(title: "Third", image: UIImage(systemName: "pencil.circle"), attributes: [], state: .off) { action in
print("third")
}
private lazy var elements: [UIAction] = [first]
private lazy var menu = UIMenu(title: "new", children: elements)
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: nil)
menu = menu.replacingChildren([first, second, third])
if #available(iOS 14.0, *) {
navigationItem.rightBarButtonItem?.menu = menu
}
}
Tried Solution from #Hangar Rash
private lazy var first = UIAction(title: "Edit", image: UIImage(systemName: "pencil.circle"), attributes: [], state: .off)
{ [unowned self] action in
self.RightSideBarButtonItemTapped(_:)
// Getting error on this line which says its " Function is unused "
}
override func viewDidLoad() {}
override func viewDidAppear(_ animated: Bool) {}
#objc func RightSideBarButtonItemTapped(_ sender:UIBarButtonItem!)
{
let vc = IceWorldView()
present(vc, animated: true)
}

Your first action has two issues:
a syntax error with action
there's no need to use #selector. Just call the method directly
Change:
private lazy var first = UIAction(title: "Edit", image: UIImage(systemName: "pencil.circle"), attributes: [], state: .off) { [self]_ in
action: #selector(self.RightSideBarButtonItemTapped(_:))
}
to:
private lazy var first = UIAction(title: "Edit", image: UIImage(systemName: "pencil.circle"), attributes: [], state: .off) { [unowned self] action in
self.RightSideBarButtonItemTapped(someButton)
}
Your RightSideBarButtonItemTapped expects a UIBarButtonItem parameter but you don't have one in the menu. You can either made a dummy button instance to pass in or you can change the RightSideBarButtonItemTapped so it doesn't take any parameters. You don't seem to use the passed in sender anyway.
Fix the use of #selector in your second action as well.

Related

ContextMenu issue in UIBarButtonItem

I put UIBarButtonItem inside Toolbar as you can see in Storyboard. I put UIMenu in UIBarButtonItem.menu. When I click a button in UIMenu, the icon of the button I clicked is transferred to UIBarButtonItem. Why is this happening?
Question:
Why does the UIBarButtonItem's icon change when I press the button from ContextMenu?
Video:
Storyboard:
UIBarButtonItem Attributes:
ViewController:
#IBOutlet weak var fooItem: UIBarButtonItem!
//MARK: Functions
override func viewDidLoad() {
super.viewDidLoad()
fooItem.menu = ContextMenuManager.shared.makeUIMenu()
}
ContextMenuManager:
protocol ContextMenuFunctionable {
func didTappedEmptyFolder()
func didTappedImportFile()
}
class ContextMenuManager {
static var shared = ContextMenuManager()
var delegate: ContextMenuFunctionable?
var emptyFolder: UIAction?
var importFile: UIAction?
var importPhotoOrVideo: UIAction?
private init() {
emptyFolder = UIAction(
title: "Empty Folder",
image: .folderBadgePlus
.applyingSymbolConfiguration(.symbolConfig),
identifier: nil,
state: .off,
handler: { _ in self.delegate?.didTappedEmptyFolder() }
)
importFile = UIAction(
title: "Import File",
image: .squareAndArrowDown.applyingSymbolConfiguration(.symbolConfig),
identifier: nil,
state: .off,
handler: { _ in self.delegate?.didTappedImportFile() }
)
importPhotoOrVideo = UIAction(
title: "Import Photo & Video",
image: .photo.applyingSymbolConfiguration(.symbolConfig),
identifier: nil,
state: .off,
handler: { _ in self.delegate?.didTappedImportFile() }
)
}
func makeUIMenu() -> UIMenu {
guard let emptyFolder, let importFile, let importPhotoOrVideo else { return UIMenu() }
return UIMenu(title: "Add",
image: .folder.applyingSymbolConfiguration(.symbolConfig),
options: [.displayInline, .singleSelection],
children: [importPhotoOrVideo, importFile, emptyFolder])
}
}
Uncheck the "Selection as Primary Action" checkbox and the button's icon should no longer change when you make a menu selection.
I replicated what you are seeing in code. I created a menu basically using your ContextMenuManager code but I created the UIBarButtonItem as follows:
let menu = UIMenu(title: "Add",
options: [.displayInline, .singleSelection],
children: [importPhotoOrVideo, importFile, emptyFolder])
let button = UIBarButtonItem(image: UIImage(systemName: "doc.on.clipboard.fill"), menu: menu)
button.changesSelectionAsPrimaryAction = false
When changesSelectionAsPrimaryAction is set to true, the button's icon reflects the chosen menu. When set to false, it retains its own icon.

How to show pull down button menu in IOS 14 swift

I created a pull down button like below
#IBOutlet weak var pullDownButton: UIButton!
Then I called a method from viewDidLoad() to configure pull down menu reference
func setupMenu() {
let add = UIAction(title: "Add", image: UIImage(systemName: "plus")) { _ in
self.showToast(message: "Add", seconds: 1.0)
}
let edit = UIAction(title: "Edit", image: UIImage(systemName: "pencil")) { _ in
self.showToast(message: "Edit", seconds: 1.0)
}
let delete = UIAction(title: "Delete", image: UIImage(systemName: "minus")) { _ in
self.showToast(message: "Delete", seconds: 1.0)
}
let menu = UIMenu(title: "Menu", children: [add, edit, delete])
pullDownButton.menu = menu
pullDownButton.showsMenuAsPrimaryAction = true
}
But I am unable to show the pull down menu upon long press or simple press.
Is there any way to show the pull down menu
Normal Button Declaration
#IBOutlet weak var btnAllocationType: UIButton!
use of this button
let menuClosure = {(action: UIAction) in
self.update(number: action.title)
}
btnAllocationType.menu = UIMenu(children: [
UIAction(title: "option 1", state: .on, handler:
menuClosure),
UIAction(title: "option 2", handler: menuClosure),
UIAction(title: "option 3", handler: menuClosure),
])
btnAllocationType.showsMenuAsPrimaryAction = true
btnAllocationType.changesSelectionAsPrimaryAction = true
and update function,where you get the selection
func update(number:String) {
if number == "option 1" {
print("option 1 selected")
}
showButton.showsMenuAsPrimaryAction = true
Declare the IBOutlet for the button:
#IBOutlet weak var filterPullDownButtom: UIButton!
Create a menu and add UIAction for each item:
override func viewDidLoad() {
super.viewDidLoad()
filterPullDownButtom.showsMenuAsPrimaryAction = true
filterPullDownButtom.changesSelectionAsPrimaryAction = true
let optionClosure = {(action: UIAction) in
if (action.index(ofAccessibilityElement: Any))
}
filterPullDownButtom.menu = UIMenu(children: [
UIAction(title: "Within 10 kms", state: .on, handler: optionClosure),
UIAction(title: "10 <= 20 kms", handler: optionClosure),
UIAction(title: "20 <= 50 kms", handler: optionClosure),
UIAction(title: "Within 100 Kms", handler: optionClosure),
])

Presenting Context menu on button tap in swift

In iOS context menus can be shown by a long press or a tap. Currently the below code shows a context menu on a long press, how do I present the menu on a tap?
let interaction = UIContextMenuInteraction(delegate: self)
tagBtn.addInteraction(interaction)
func contextMenuInteraction(_ interaction: UIContextMenuInteraction,
configurationForMenuAtLocation location: CGPoint)
-> UIContextMenuConfiguration? {
let favorite = UIAction(title: "Favorite",
image: UIImage(systemName: "heart.fill")) { _ in
// Perform action
}
let share = UIAction(title: "Share",
image: UIImage(systemName: "square.and.arrow.up.fill")) { action in
// Perform action
}
let delete = UIAction(title: "Delete",
image: UIImage(systemName: "trash.fill"),
attributes: [.destructive]) { action in
// Perform action
}
return UIContextMenuConfiguration(identifier: nil,
previewProvider: nil) { _ in
UIMenu(title: "Actions", children: [favorite, share, delete])
}
}
UIContextMenuInteraction is intended only for contextual (long-press) menus.
If you want the primary action of the button to display a menu, you can just create a UIMenu and assign it directly to the button.menu property, then set button.showsMenuAsPrimaryAction = true, like this:
let favorite = UIAction(title: "Favorite",
image: UIImage(systemName: "heart.fill")) { _ in
// Perform action
}
...
let button = UIButton()
button.showsMenuAsPrimaryAction = true
button.menu = UIMenu(title: "", children: [favorite, ...])

Swift: Try to use closure instead #selector but not working

I'm trying to use a closure instead selector but it does not work. The print does not work can you help me
My custom Action:
final class Action: NSObject {
private let _action: () -> ()
init(action: #escaping () -> ()) {
_action = action
super.init()
}
#objc func action() {
_action()
}
}
Using:
let menu = NSMenu()
let action = Action { print("My action") }
menu.addItem(NSMenuItem(title: "Delete", action: #selector(action.action), keyEquivalent: ""))
tableView.menu = menu
When I click on the menu, the delete option does not print, why does not it work?
Try setting a target for the NSMenuItem. As per the Apple documentation, this doesn’t seem to be included in the initializer but can be set afterwards.
let menu = NSMenu()
let action = Action { print("My action") }
var menuItem = NSMenuItem(title: "Delete", action: #selector(action), keyEquivalent: "")
menuItem.target = action // This refers to the action instance
menu.addItem(menuItem)
tableView.menu = menu
This is not possible because selectors are just names of methods, not methods themselves.
BUT there is another way to use a closure with #selector
/// Target-Action helper.
final class Action: NSObject {
private let _action: () -> ()
init(action: #escaping () -> ()) {
_action = action
super.init()
}
#objc func action() {
_action()
}
}
let action1 = Action { print("action1 triggered") }
let button = UIButton()
button.addTarget(action1, action: #selector(action1.action), forControlEvents: .TouchUpInside)

Swift 3 - Reload NavigationController Button Image

I have a navigation controller view, with an embedded collectionview controller. I have a right bar button added programmatically, with an image added to it as follows:
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "greyCircle").withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector (handleGrayCircleButton))
I would like a user that clicks on this button, to be able to change the image from grey to silver. I have the button handler set up to switch from grey to silver with each press:
self.navigationItem.rightBarButtonItem?.image = UIImage(named: "greyCircle")
However, the image only changes briefly before switching back. I have tried to save the color of the image as a 'flag' variable, i.e. if it is currently grey, turn to silver, in order to make the change persist, however, the image continually resets to whatever it is loaded with (either with viewDidLoad or viewWillAppear).
Is there anyway to refresh/reload the navigationController tab bar after each click and make it persist, along with the images for the bar buttons?
#
EDIT: Following is full button handler.
func handleFavoritePress(){
print("Favorites pressed")
if(!returnTrueIfInFavorites(objectName: readString(object: self.patientRecord!, key: "name"), objectType: LIST_CoreDataBaseObjects.Patients)){
// Add to favorites
Favorites_Names.append(readString(object: self.patientRecord!, key: "name"))
Favorites_Types.append(LIST_CoreDataBaseObjects.Patients)
// Let user know
let patientName = readString(object: self.patientRecord!, key: "name")
let alertController = UIAlertController(title: "Alert!", message: "\(patientName) has been added to your favorites.", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(defaultAction)
var rootViewController = UIApplication.shared.keyWindow?.rootViewController
if let navigationController = rootViewController as? UINavigationController {
rootViewController = navigationController.viewControllers.first
}
if let tabBarController = rootViewController as? UITabBarController {
rootViewController = tabBarController.selectedViewController
}
rootViewController?.present(alertController, animated: true, completion: nil)
self.navigationItem.rightBarButtonItem?.image = UIImage(named: "savedFavorite_1")
}else{
self.navigationItem.rightBarButtonItem?.image = UIImage(named: "favorite_2")
deleteFromFavorites(objectName: readString(object: patientRecord!, key: "name"), objectType: LIST_CoreDataBaseObjects.Patients)
}
for favorite in Favorites_Names{
print("Favorites are currently: \(patient)")
}
}
And other functions:
override func viewWillAppear(_ animated: Bool) {
collectionView?.backgroundView = setBackgroundImage(imageName: "whiteBackground")
navigationItem.title = "Patient"
if(returnTrueIfInFavorites(objectName: readString(object: patientRecord!, key: "name"), objectType: LIST_CoreDataBaseObjects.Patients)){
setupNavBarButtonsIsFavorited()
}else{
setupNavBarButtonsNotFavorited()
}
}
func setupNavBarButtonsNotFavorited(){
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "favorite_2").withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector (handleFavoritePress))
}
func setupNavBarButtonsIsFavorited(){
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "savedFavorite_1").withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector (handleFavoritePress))
}
The answer to your question lies within this two LIST_CoreDataBaseObjects.Patients, returnTrueIfInFavorites or the patientRecord variable.
Either LIST_CoreDataBaseObjects.Patients did not get updated on your function handleFavoritePress's code
/* this code */
Favorites_Names.append(readString(object: self.patientRecord!, key: "name"))
Favorites_Types.append(LIST_CoreDataBaseObjects.Patients)
/* this code */
Because from what you've explained in the comments section, if I understood it correctly, is that when you tap the button the image changes, and then you navigate to a different view, but then when you navigate back, viewDidAppear(_:) and viewWillAppear(_:) will be triggered.
Which in the code block below I will explain
// this function will be triggered everytime you navigate into this view (because it has appeared)
override func viewWillAppear(_ animated: Bool) {
/* don't forget to call super.viewWillAppear(true) */
super.viewWillAppear(true)
/* don't forget to call super.viewWillAppear(true) */
collectionView?.backgroundView = setBackgroundImage(imageName: "whiteBackground")
navigationItem.title = "Patient"
// the problem lies here
/* the function `(returnTrueIfInFavorites(objectName: readString(object: patientRecord!, key: "name"), objectType: LIST_CoreDataBaseObjects.Patients))` is always returning one thing which is why your image returns back to its previous state */
if (returnTrueIfInFavorites(objectName: readString(object: patientRecord!, key: "name"), objectType: LIST_CoreDataBaseObjects.Patients)) {
setupNavBarButtonsIsFavorited() // only one of these gets called
} else {
setupNavBarButtonsNotFavorited() // only one of these gets called
}
}
func setupNavBarButtonsNotFavorited() {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "favorite_2").withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector (handleFavoritePress))
}
func setupNavBarButtonsIsFavorited() {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "savedFavorite_1").withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector (handleFavoritePress))
}
All of these or you're just not updating the variable self.patientRecord!'s value (I haven't seen any setting in your code relating to this variable)