#selector doesn't see my own created method in NSMenuItem - swift

I have a problem creating a NSMenuItem actions, I have created function, which creates Menu for me, while using init on NSMenuItem with selector, my described function doesn't work on swift 4, if I use NSApp.terminate - NSMenuItem sees selector works normally()... Here is the code:
mainMenuController: NSObject {
func setUpMenus(){
//...
let firstItemMainMenu = NSMenuItem.init(title: "Test", action: #selector(test), keyEquivalent: "") // This one doesn't work
let fourthItemMainMenu = NSMenuItem.init(title: "Quit", action: #selector(NSApp.terminate), keyEquivalent: "q") //This one NSApp.terminate - works
//...
}
and here is the function:
#objc func test(_ sender: Any?){
let alert = NSAlert()
alert.addButton(withTitle: "test")
alert.messageText = "test"
alert.runModal()
}
}
Any ideas?

Two suggestions:
Use the syntax Selector("terminate:") which hands the selector over to First Responder (you will get a warning to use #selector syntax)
Create an extra method #objc func quit which calls NSApp.terminate

Related

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)

Actions assigned to NSMenuItem dont seem to work

Heres whats going on:
I am attempting to build a Mac Status Bar App completely programmatically. Everything seems to be working fine, that is the menu shows up in the Mac status bar, the dropdown menu is displaying how it should. But when I click on the menu items, nothing happens. I even changed the target function to just doing the basic task of printing to the terminal, and nothing.
About the code:
The issue lies somewhere around here I think:
menu.addItem(NSMenuItem(title: val, action: #selector(toggleService), keyEquivalent: ""))
That code should fire off the > toggleService function. But it doesn't do anything. Could the issue be due to the fact that I am only inheriting from the NSObject class?
The Code
// StatusBar.swift
import Cocoa
class StatusBar: NSObject {
var menuButton = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
var menu = NSMenu()
var service = Service()
override init() {
super.init()
menuButton.button?.image = NSImage(named: NSImage.Name("icon"))
menuButton.menu = menu
menu.autoenablesItems = false
for (_, val) in service.list {
menu.addItem(NSMenuItem(title: val, action: #selector(toggleService), keyEquivalent: ""))
}
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Quit", action: #selector(quit), keyEquivalent: ""))
}
#objc func toggleService(sender: NSMenuItem) {
print ("Say Something.. anything??")
}
#objc func quit(sender: NSMenuItem) {
print ("Say Something.. anything??")
}
}
menuItem.target = self
You need to set the target to 'self'. NSMenuItems have two basic requirements. An action, and a target,
Action
menuItem.action: #selector(YOURFUNCTION)
Target
menuItem.target = self
So to get your menu items working, replace the for loop (within your init call) with this new one:
for (_, val) in service.list {
let menuItem = menu.addItem(NSMenuItem(title: val, action: #selector(toggleService), keyEquivalent: ""))
menuItem.target = self
}

selector with argument gestureRecognizer

Argument of '#selector' does not refer to an '#objc' method, property,
or initializer
Problem : Above Error when i try to pass argument with selector
code snippet:
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(labelPressed(i: 1)))
func labelPressed(i: Int){
print(i)
}
You cannot pass a parameter to a function like that. Actions - which this is, only pass the sender, which in this case is the gesture recognizer. What you want to do is get the UIView you attached the gesture to:
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(labelPressed())
func labelPressed(_ recognizer:UITapGestureRecognizer){
let viewTapped = recognizer.view
}
A few more notes:
(1) You may only attach a single view to a recognizer.
(2) You might want to use both the `tag` property along with the `hitTest()` method to know which subview was hit. For example:
let view1 = UIView()
let view2 = UIView()
// add code to place things, probably using auto layout
view1.tag = 1
view2.tag = 2
mainView.addSubview(view1)
mainView.addSubview(view2)
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(mainViewTapped())
func mainViewTapped(_ recognizer:UITapGestureRecognizer){
// get the CGPoint of the tap
let p = recognizer.location(in: self)
let viewTapped:UIView!
// there are many (better) ways to do this, but this works
for view in self.subviews as [UIView] {
if view.layer.hitTest(p) != nil {
viewTapped = view
}
}
// if viewTapped != nil, you have your subview
}
You just should declare function like this:
#objc func labelPressed(i: Int){ print(i) }
Update for Swift 3:
Using more modern syntax, you could declare your function like this:
#objc func labelTicked(withSender sender: AnyObject) {
and initialize your gesture recognizer like this, using #selector:
UITapGestureRecognizer(target: self, action: #selector(labelTicked(withSender:)))

Custom class to create an Alert with Buttons. How to add a completition handler?

I was trying to create a custom alert and I am getting mad trying to implement a completition handler on the buttons. I have tried a few things, the last, create func array to pass in the selector addTarget function of the UIButton, but not working. (where the ****** are)
The issue: "Argument of #selector does no refer to #obc method, property or initializer"
The difficult coding part I can't do is to configure the selector with some code I receive from my view controller where I create an object with the class below.
class Alert: NSObject {
func showAlert(){
if let window = UIApplication.shared.keyWindow {
//configure some constraints and animations
}
var buttons: [UIButton] = []
var buttonsFunc: [() -> Void ] = []
func addNewButton(title: String, handler: #escaping () -> Void) {
buttons.append(createButton(title: title))
buttonsFunc.append {
self.dismissAlert()
handler()
}
}
func setupButtons() {
for (index, button) in buttons.enumerated() {
boxView.addSubview(button)
//Here is the problem ***************************
button.addTarget(self, action: #selector(buttonsFunc[index]), for: .touchUpInside)
//More constraints(not important)
button.centerXAnchor.constraint(equalTo: boxView.centerXAnchor).isActive = true
button.widthAnchor.constraint(equalTo: (button.titleLabel?.widthAnchor)!).isActive = true
button.heightAnchor.constraint(equalToConstant: 25).isActive = true
}
}
func dismissAlert(){
//Animation to dismiss my alert
}
Other functions:
//Even if its not important the function i use to create the button
func createButton(title: String) -> UIButton {
let button = UIButton(type: .system)
button.backgroundColor = .clear
button.setTitle(title, for: .normal)
button.titleLabel?.sizeToFit()
button.setTitleColor(uPBlue, for: .normal)
button.titleLabel?.font = UIFont(name: uPFont, size: 20)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}
Any ideas how to fix this?
Or maybe a totally different way.
If it works i take it.
So a selector is just the name of a function. A closure by definition is an anonymous function so you can't quite do it this way.
Lets try another route, define a new function to give to the buttons:
private func buttonPressed(sender: UIButton) {
}
Then lets give the buttons this function instead of the closure:
...
button.addTarget(self, action: #selector(Alert.buttonPressed(sender:)), for: .touchUpInside)
...
Now we can take advantage of tuples here. Instead of having two separate arrays we'll to one array with an adhoc data structure for our buttons:actions:
// var buttons: [UIButton] = []
// var buttonsFunc: [() -> Void ] = []
// Becomes
var buttonActionArray: [(button: UIButton, action: () -> Void)] = []
Now lets implement buttonPressed(sender:)
private func buttonPressed(sender: UIButton) {
for buttonTuple in buttonActionArray {
if buttonTuple.button === sender {
buttonTuple.action()
}
}
}

How to make a gestureRecogniser selector work in swift 3?

Below is the code I have been using to try and add a gesture recogniser to something. I am getting the yellow error: "No method declared with objective C selector dragging" and then the program is crashing when I go to pan on it. The code and way of using a selector seems to work in all tutorials but it's the problem here.
class GameViewController: UIViewController, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let p = UIPanGestureRecognizer(target: self, action: #selector("dragging"))
p.delegate = self
characterGridView!.addGestureRecognizer(p)
}
func dragging(p: UIPanGestureRecognizer) {
print("works")
}
Your selector is incorrect.
Change
let p = UIPanGestureRecognizer(target: self, action: #selector("dragging"))
to
let p = UIPanGestureRecognizer(target: self, action: #selector(dragging(p:)))