I'm using the code below to show the status bar menu on the right click and show the application on the left click.
To achieve that, we need to detach the menu (statusBarItem.menu = nil) once it is closed. Otherwise, we would stop receiving statusBarButtonDidClick event and left-click would not work.
However, when I attach an external screen after right-click menu is getting closed immediately. The problem is in the line: statusBarItem.button?.performClick(nil), but without that menu would not appear instantly.
My guess is that performClick is trying to click on another screen which closes the menu on the active screen.
Is there any way to fix it or achieve the same functionality without performClick?
func createStatusBarItem() {
statusBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.squareLength))
if let button = statusBarItem.button {
button.image = Asset.Icons.statusbar.image
button.image?.size = CGSize(width: 16, height: 16)
button.target = self
button.action = #selector(statusBarButtonDidClick)
}
statusBarItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])
}
func menuDidClose(_ menu: NSMenu) {
statusBarItem.menu = nil
}
#objc private func statusBarButtonDidClick(sender: NSStatusBarButton) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp {
let statusBarMenu = StatusBarMenu()
statusBarMenu.delegate = self
statusBarItem.menu = statusBarMenu
statusBarItem.button?.performClick(nil)
} else {
application.showApplication()
}
}
I am having a no-show menuController and I have checked all of the suggestions in previous questions. It turns out the imageView I have implemented a UILongPressGestureRecognizer on, to show the menu, is returning False on calling .becomeFirstResponder just before setting up the menu controller.
I am coding in swift 4 and can't figure out how to make the imageView return True to calling .becomeFirstResponder. Help!
/*********************************************************/
override func viewDidLoad() {
super.viewDidLoad()
// long tap to show menu that enables deletion of the image.
imageView_1.isUserInteractionEnabled = true
let longPressRecogniser = UILongPressGestureRecognizer(target: self, action: #selector(longPressOnImage(_:)))
//longPressRecogniser.numberOfTapsRequired = 1
//longPressRecogniser.numberOfTouchesRequired = 1
longPressRecogniser.minimumPressDuration = 0.5
imageView_1.addGestureRecognizer(longPressRecogniser)
imageView_1.image = placeHolderImage_1
imageView_2.image = placeHolderImage_2
}
/*********************************************************/
#IBAction func longPressOnImage(_ gestureRecognizer: UILongPressGestureRecognizer) {
print(#function)
if gestureRecognizer.state == .began {
//print("gestureRecognizer.state == .began")
self.tappedView = gestureRecognizer.view!
if tappedView.canResignFirstResponder {
print("can resign first responder")
}
if tappedView.becomeFirstResponder() {
print("returned TRUE to becomeFirstResponder")
} else {
print("returned FALSE to becomeFirstResponder")
}
// Configure the shared menu controller
let menuController = UIMenuController.shared
// Configure the menu item to display
// Create a "delete" menu item
let deleteImage = UIMenuItem(title: "Delete", action: #selector(deleteImage_1))
menuController.menuItems = [deleteImage]
// Set the location of the menu in the view.
let location = gestureRecognizer.location(in: tappedView)
print("location = ", location)
let menuLocation = CGRect(x: location.x, y: location.y, width: 2, height: 2)
menuController.setTargetRect(menuLocation, in: tappedView)
//update the menu settings to force it to display my custom items
menuController.update()
// Show the menu.
menuController.setMenuVisible(true, animated: true)
print("menu should be visible now")
}
}
/*********************************************************/
#objc func deleteImage_1() {
print(#function)
}
My caveman debugging print statements output:
longPressOnImage
can resign first responder
returned FALSE to becomeFirstResponder
location = (207.0, 82.0)
menu should be visible now
Create a custom imageView class and override "canBecomeFirstResponder" property like this:
class ResponsiveImage : UIImageView{
override var canBecomeFirstResponder: Bool{
return true
}
}
Use this ResponsiveImage type and your code will work :)
Thank you to adri. Your answer is the solution to my problem.
I had read in other posts to similar questions about overriding var canBecomeFirstResponder but either overlooked it or it wasn't made explicit that a custom UIImageView class needs to be created.
Just to make it clear to newbies like me, the class of the imageView in storyBoard and its #IBOutlet in its viewController must typed as ResponsiveImage. If only one of these is changed a type casting error is reported.
Many thanks for ending my hours of frustration! :-)
I'm trying to display a list of system voices, but I'd like to group them by region.
This is an example of select in html.
Ideally I'd like to create a dropdown that is similar to accessibility language selection.
Is there any way to replicate this in Interface Builder / swift?
Any pointers would be appreciated.
Update:
The reason for this, is because I am displaying a list of speech voices to the user. At the moment, it mixes all the regions together, which is very confusing.
There is an update I'm working on, where I can display "English (United Kingdom)", but I'd like to group them up before releasing it.
The following code groups menu, but not like the way you mentioned.
let items = [["First","Second"],["First","Second"],["First","Second"]]
lazy var addNewViewButton : NSPopUpButton = {
let popupButton = NSPopUpButton()
let firstMenuItem = NSMenuItem(title: "First Group", action: nil, keyEquivalent: "")
let secondMenuItem = NSMenuItem(title: "Second Group", action: nil, keyEquivalent: "")
let thirdMenuItem = NSMenuItem(title: "Third Group", action: nil, keyEquivalent: "")
let superMenu = NSMenu()
superMenu.addItem(firstMenuItem)
superMenu.addItem(secondMenuItem)
superMenu.addItem(thirdMenuItem)
for (index,item) in items.enumerated()
{
let menu = NSMenu()
for title in item
{
let menuItem = NSMenuItem(title: title, action: nil, keyEquivalent: "")
menuItem.target = self
menu.addItem(menuItem)
}
menu.addItem(NSMenuItem.separator())
superMenu.setSubmenu(menu, for: superMenu.items[index])
}
popupButton.menu = superMenu
popupButton.translatesAutoresizingMaskIntoConstraints = false
return popupButton
}()
Add the popupbutton in your code and you will get results like this
Each one will be having its own items inside.
I'm going to offer an answer in Objective-C because I'm not up on Swift. Sorry... You ought to be able to translate it easily enough, though, or I'm sure someone here can do it.
So, I managed this by making a subclass of NSMenu with the following method:
#implementation MenuWithSections
- (NSMenuItem*)insertItemWithTitle:(NSString*)aString action:(SEL)aSelector keyEquivalent:(NSString*)keyEquiv atIndex:(NSInteger)index
{
NSMenuItem * item;
NSString * adjustedString;
if ([aString isEqualToString:menuDividerString]) {
NSMenuItem *separator = [NSMenuItem separatorItem];
[self insertItem:separator atIndex:index];
return separator;
} else if ([aString hasPrefix:menuSectionHeaderPrefix]) {
adjustedString = [[aString substringFromIndex:menuSectionHeaderPrefix.length] capitalizedString];
NSMenuItem * sectionHead = [[NSMenuItem alloc] initWithTitle:adjustedString
action:nil
keyEquivalent:#""];
sectionHead.enabled = NO;
sectionHead.indentationLevel = 0;
[self insertItem:sectionHead atIndex:index];
return sectionHead;
}
item = [super insertItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv atIndex:index];
item.indentationLevel = 1;
return item;
}
#end
menuDividerString in my case is #"----", and menuSectionHeaderPrefix is #".."; see the examples below.
To make it work, do three things:
In IB, drill down the popup button until you see its menu, and set the class of the menu to 'MenuWithSections'.
popup button drill-down
Turn off the popup button's 'autoenable' feature (click it off in the 'Attributes' tab of IB, or set it to NO programmatically)
Pass in modified strings using the above constants to get the desired effect.
In other words, to get the look you want in your question, pass the popup button titles like so:
[self.popbutton addItemsWithTitles:#[ #"..swedish cars", #"Volvo", #"Saab", #"..German cars", #"Mercedes", #"Audi"]];
sectioned popup
or if you want a divider-line between the two groups, use:
[self.popbutton addItemsWithTitles:#[ #"..swedish cars", #"Volvo", #"Saab", #"----", #"..German cars", #"Mercedes", #"Audi"]];
sectioned popup with divider
If you don't have the group heading, you can use the following code
let items = [["First","Second"],["First","Second"],["First","Second"]]
lazy var addNewViewButton : NSPopUpButton = {
let popupButton = NSPopUpButton()
let menu = NSMenu()
for item in items
{
for title in item
{
let menuItem = NSMenuItem(title: title, action: nil, keyEquivalent: "")
menuItem.target = self
menu.addItem(menuItem)
}
menu.addItem(NSMenuItem.separator())
}
popupButton.menu = menu
popupButton.translatesAutoresizingMaskIntoConstraints = false
return popupButton
}()
Result :
I create a NSStatusItem in a class named StatusMenuController like this
class StatusMenuController: NSObject {
let statusItem = NSStatusBar.system().statusItem(withLength: NSVariableStatusItemLength)
override func awakeFromNib() {
let icon = NSImage(named: "MenuBar")
icon?.isTemplate = true // best for dark mode
if let button = statusItem.button {
button.image = icon
button.action = #selector(StatusMenuController.showPomoNow)
button.sendAction(on: [.leftMouseUp, .rightMouseUp])
button.target = self
}
statusItem.title = "25:00"
}
I can change the title easily in this class. It will change like I expected.
I reference this class in AppDelegate
let statusMenu = StatusMenuController()
Add change it in other class
statusMenu.statusItem.title = "24:31"
I expect the text 25:00 will change to "24:31",but it looks like this.screenshot
System create another NSStatusItem. How can I change the one which created in StatusMenuController.
OOPer‘s comment is right. I found a object which create another StatusMenuController from Storyboard. I delete the object,Then everything is OK.
I created a NSStatusBarItem and popUp a (programmatically generated) NSMenu on right click:
let statusBarItem = NSStatusBar.system().statusItem(withLength: -1)
statusBarItem.action = #selector(AppDelegate.statusBarItemAction(sender:))
let menu = NSMenu()
var menuItem = NSMenuItem()
menuItem.action = #selector(AppDelegate.customItemAction)
menu.addItem(menuItem)
func statusBarItemAction(sender: NSStatusItem) {
let mouseEvent = NSEvent.pressedMouseButtons()
if mouseEvent == 2 {
// right click
lxStatusBarItem.popUpMenu(menu)
}
}
func customItemAction() {
// do something
}
Everything works fine, except that the statusBarItem remains highlighted after customItemAction is called:
How can I solve this?
I found that setting statusItem.button?.isHighlighted = false helped with removing the highlight. In your case, this would look like this:
func customItemAction() {
// do something
statusBarItem.button?.isHighlighted = false
...
}