Swift - detect is NSStatusItem hidden - swift

I'm working on MacOS menu bar app which can have variable length:
#IBOutlet weak var statusMenu: NSMenu!
let statusItem: NSStatusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
statusItem.title = "Long text..."
statusItem.menu = statusMenu
If the status item title length will be to long to display and the menu bar item will hide I'd like to change the title to the shortened version. So I need:
check if statusItem is visible
if no, check how much free space I have to adjust the shortening
change the title
Point 3 is easy. I have problems with point 1 and 2. None of the stackoverflow answers work for me. Is there any way how this can be done?

Related

How do you create a Status Bar icon on macOS with SwiftUI?

I am trying to create an application that starts with a Status Bar icon, and without a application window. It is for a macOS utility that will be invoked from menus displayed from the Status Bar icon.
Can this be done entirely within SwiftUI, or must I use AppKit?
This is what it looks like in AppKit / Cocoa
theItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
let theStatusButton = theItem!.button
theStatusButton?.title = "MyApp"
let menuItemOne = NSMenuItem(title: "Hey MyApp", action: nil, keyEquivalent: "")
statusMenu = NSMenu(title: "This to do in MyApp")
statusMenu!.addItem(menuItemOne)
theItem!.menu = statusMenu!
Thanks in advance for any help.
macOS 13.0+, Xcode 14.0+
Use SwiftUI's MenuBarExtra structure to create a Status Bar icon and menu on macOS Ventura.
struct MenuBarExtra<Label, Content> where Label : View, Content : View
Look at my post for details.

How to programmatically set an NSPopUp title. Swift4 OSX

I have an IB NSPopUpButton with a list of items and a number of NSTextFields.
The items from the popup are used to populate the text fields (ingredients) until a recipe is complete.
I would like to reset the popup title after each item is used but cannot find the code and syntax to do this.
I'd also like to be able to click on any of the text fields after selecting an ingredient to drop it there. Drag and drop from the popup would be ideal but I cannot find a simple way to do either so am currently using another button adjacent to each text field to initiate the drop.
This is neither elegant nor ideal.
Hopefully someone can propose better solutions.
My code currently looks like this
var ItemLabel: String = ""
#IBAction func Ingredients(_ sender: NSPopUpButton){
ItemLabel = sender.titleOfSelectedItem ?? "Nil"
}
#IBOutlet weak var Ingredient1: NSTextField!
#IBAction func AddIngredient1(_ sender: NSButton){ // button next to text field
Ingredient1Label.stringValue = ItemLabel
// Here I need to reset the popup title
}

How to show two row of text in a menu bar app in mac os

can you explain to me how to make an app on osx (with swift 3) showing only (without icon on the dock) two row of text in the menu bar like in the picture below ? or at least how to show text on the menu bar ?
OSX Menu bar picture:
PS: I am new on osx app dev so don't hesitate to be very verbose ;)
This kind of app is called menu bar app or menulet.
The dockless appearance is set by the key LSUIElement (1) in Info.plist
Use a custom NSView
Assign the view as a subview to the view of the button of the NSStatusItem
Draw the text in drawRect
Important: Using a custom view requires to implement all methods to handle the highlighting, mouse clicks and NSMenu delegate.
Here is an article how to do it, it's in Objective-C but this might be a starting point:
Adding a Custom View to an NSStatusItem
var statusBarItem: NSStatusItem?
func applicationDidFinishLaunching(_ notification: Notification) {
statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let button = self.statusBarItem?.button {
let style = NSMutableParagraphStyle()
style.maximumLineHeight = 10
style.alignment = NSTextAlignment.left
let attributes = [NSAttributedString.Key.paragraphStyle: style, NSAttributedString.Key.font: NSFont.systemFont(ofSize: 10.0), NSAttributedString.Key.baselineOffset: -5] as [NSAttributedString.Key : Any]
let textString = "Line1\nLine2"
let attributedTitle = NSAttributedString(string: textString, attributes: attributes)
button.attributedTitle = attributedTitle
}
}

Pop a View when click the StatusBar Item

I expect to present a View when click the StatusBar Item like this:
And I have set up the StatusItem
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var imageManager: NSMenu!
let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(-1)
func applicationDidFinishLaunching(aNotification: NSNotification) {
let icon = NSImage(named: "statusIcon")
icon?.template = true
statusItem.image = icon
statusItem.menu = imageManager
statusItem.action = nil
}
What should I do next?
The simplest approach would be to create a NSPopover object and attach it to the status item. There are multiple tutorials online for this. Here is a good one!
Although, if you need a more custom appearance, you need to write your own. For this you will need a borderless window with transparent background. Its contentView will draw the frame of the speech-bubble ish design..

Tooltip doesn't show up again

I have a Mac app that exclusively live on the menu bar. It has a progress bar and a label. The label shows the percentage of the progress of the task that's being carried out. I want to show more info when the user hovers the mouse pointer over the progress indicator.
When I set the tooltip initially and hover over, it displays without an issue.
But if I head over somewhere and open the menu app again and hover over again, the tooltip doesn't come up. I can't figure out why. Here's my code.
ProgressMenuController.swift
import Cocoa
class ProgressMenuController: NSObject {
#IBOutlet weak var menu: NSMenu!
#IBOutlet weak var progressView: ProgressView!
let menuItem = NSStatusBar.systemStatusBar().statusItemWithLength(NSVariableStatusItemLength)
var progressMenuItem: NSMenuItem!
override func awakeFromNib() {
menuItem.menu = menu
menuItem.image = NSImage(named: "icon")
progressMenuItem = menu.itemWithTitle("Progress")
progressMenuItem.view = progressView
progressView.update(42)
}
#IBAction func quitClicked(sender: NSMenuItem) {
NSApplication.sharedApplication().terminate(self)
}
}
ProgressView.swift
import Cocoa
class ProgressView: NSView {
#IBOutlet weak var progressIndicator: NSProgressIndicator!
#IBOutlet weak var progressPercentageLabel: NSTextField!
func update(value: Double) {
dispatch_async(dispatch_get_main_queue()) {
self.progressIndicator.doubleValue = value
self.progressIndicator.toolTip = "3 out of 5 files has been copied"
self.progressPercentageLabel.stringValue = "\(value)%"
}
}
}
This is a demo app similar to my actual app. So the update() function is called only once and the values are hardcoded. But in my actual app, the progress is tracked periodically and the update() function gets called with it to update the values. The label's percentage value and the progress indicator's value get updated without a problem. The issue is only with the tooltip.
Is this expected behavior or am I missing something?
I ran into the same problem, and realized the issue was that only the currently focused window will display tool-tips, but after my app lost focus, it would never get it back. Focus usually transfers automatically when the user clicks on your window, but it isn't automatic for menu bar apps. Using NSApp.activate, you can regain focus onto your app:
override func viewWillAppear() {
super.viewWillAppear()
NSApp.activate(ignoringOtherApps: true)
}
sanche's answer worked for me as well, but I ended up moving the tool tips to my NSMenuItems instead so I wouldn't have to steal focus from the foreground app. NSMenuItem's tool tips seem to be handled as a special case so the app doesn't need to be focused.
This solution would make the tool tip apply to everything in the menu item and appear next to the menu rather than over it, but it looks like that might not be a problem in your case.