0

As you can see from the image I have a program that should open as menubar, I would like to know if it was possible to add a badge as seen in image two to the menubar, to indicate that there are notifications.

I find nothing on the documentation.

Can you give me a hand?

enter image description here

enter image description here

StatusBarController

import AppKit
import SwiftUI

class StatusBarController {
    @ObservedObject var userPreferences = UserPreferences.instance
    private var statusBar: NSStatusBar
    var statusItem: NSStatusItem
    private var popover: NSPopover
    
    init(_ popover: NSPopover) {
        self.popover = popover
        statusBar = NSStatusBar.init()
        statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
        
        if let statusBarButton = statusItem.button {
            statusBarButton.image = #imageLiteral(resourceName: "Fork")
            statusBarButton.image?.size = NSSize(width: 18.0, height: 18.0)
            statusBarButton.image?.isTemplate = true
            statusBarButton.action = #selector(togglePopover(sender:))
            statusBarButton.target = self
            statusBarButton.imagePosition = NSControl.ImagePosition.imageLeft
        }
    }
    
    @objc func togglePopover(sender: AnyObject) {
        if(popover.isShown) {
            hidePopover(sender)
        }else {
            showPopover(sender)
        }
    }
    
    func showPopover(_ sender: AnyObject) {
        if let statusBarButton = statusItem.button {
            popover.show(relativeTo: statusBarButton.bounds, of: statusBarButton, preferredEdge: NSRectEdge.maxY)
        }
    }
    
    func hidePopover(_ sender: AnyObject) {
        popover.performClose(sender)
    }
    
}

AppDelegate

import Cocoa
import SwiftUI

@main
class AppDelegate: NSObject, NSApplicationDelegate {
    var statusBar: StatusBarController?
    var popover = NSPopover.init()
    
    var timer: Timer? = nil

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        let contentView = ContentView()
        popover.contentSize = NSSize(width: 360, height: 360)
        popover.contentViewController = NSHostingController(rootView: contentView)
        statusBar = StatusBarController.init(popover)
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }
}
0

1 Answer 1

1

You have to do it manually

Create a custom badge view. In drawRect you have to play with the position of the badge and the size of the number.

class BadgeView: NSView {
    
    var number : Int {
        didSet {
            if oldValue != number { needsDisplay = true }
        }
    }
    
    init(frame frameRect: NSRect, number : Int) {
        self.number = number
        super.init(frame: frameRect)
    }
    
    required init?(coder: NSCoder) {
        self.number = 0
        super.init(coder: coder)
    }
    
    override func draw(_ dirtyRect: NSRect) {
        let fillColor = NSColor.systemRed
        let path = NSBezierPath(ovalIn: NSRect(x: 3, y: 4, width: 14, height: 14))
        fillColor.set()
        path.fill()
        let one = "\(number)"
        let attribs : [NSAttributedString.Key:Any] = [.font : NSFont.systemFont(ofSize: 11.0), .foregroundColor : NSColor.white]
        let xOrigin = (number > 9) ? 3.5 : 6.5
        one.draw(at: NSPoint(x: xOrigin, y: 4.5), withAttributes: attribs)
    }
}

In the controller class add a property and a function to set the number

private var badgeView : BadgeView?

func setBadge(num : Int)
{
    if num == 0 {
        if let view = badgeView {
            view.removeFromSuperview()
            badgeView = nil
        }
    } else {
        if let badgeView = badgeView {
            badgeView.number = num
        } else {
            badgeView = BadgeView(frame: NSRect(x: 0, y: 0, width: 19, height: 22), number: num)
            statusItem.button!.addSubview(badgeView!)
        }
    }
}
Sign up to request clarification or add additional context in comments.

14 Comments

How do I call it, for example in the ContentView in the Init function? I tried this, but it fails: self.StatusBarController.setBadge (num: 21)
You need a reference to the real instance of StatusBarController. You could pass it to ContentViewas a parameter
I did two tests: 1) Try calling it inside AppDelegate in applicationDidFinishLaunching So: statusBar = StatusBarController.init(popover) statusBar?.setBadge(num: 21)
As I said the code assumes that the badge image (the red circle) is in the asset catalog.
2) Do such a thing: i.sstatic.net/g1h6s.png Create an instance and access it.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.