Mouseover or hover effect on NSButton with Swift

I'm developing an OS X application with Swift. I like how Dropbox implemented their "pause/resume syncing" button, so I would like to implement that effect with a changing button text and image when the button is hovered.

Here is a demo video of the result:

We will be using NSTrackingArea to track mouse events. We just need to create an instance of NSTrackingArea within the bounds of the button and attach it to the button.

class PopoverViewController: NSViewController {  
    @IBOutlet weak var startStopButton: NSButton!

    override func viewDidLoad() {
        super.viewDidLoad()

        let area = NSTrackingArea.init(rect: startStopButton.bounds, options: [NSTrackingAreaOptions.MouseEnteredAndExited, NSTrackingAreaOptions.ActiveAlways], owner: self, userInfo: nil)
        startStopButton.addTrackingArea(area)
    }

Next, we need to implement mouseEntered and mouseExited methods.

override func mouseEntered(theEvent: NSEvent) {  
    print("Entered")
}

override func mouseExited(theEvent: NSEvent) {  
    print("Exited")
}

Rebuild and run the app to ensure it's working properly.

I'm providing a full listing of my view controller class.

//  PopoverViewController.swift

import Cocoa

let kImageStatusRunning = "NSStatusAvailable"  
let kImageStatusStopped = "NSStatusNone"

let kTextStatusPause = "Pause timer"  
let kTextStatusResume = "Resume timer"

class PopoverViewController: NSViewController {  
    @IBOutlet weak var startStopButton: NSButton!

    let appDelegate = NSApplication.sharedApplication().delegate as! AppDelegate

    override func viewDidLoad() {
        super.viewDidLoad()

        let area = NSTrackingArea.init(rect: startStopButton.bounds, options: [NSTrackingAreaOptions.MouseEnteredAndExited, NSTrackingAreaOptions.ActiveAlways], owner: self, userInfo: nil)
        startStopButton.addTrackingArea(area)
    }

    override func viewWillAppear() {
        super.viewWillAppear()

        handleButtonState()
    }

    func handleButtonState() {
        if appDelegate.isTimerRunning() {
            startStopButton.state = NSOffState
            startStopButton.title = appDelegate.getStatusText()
            startStopButton.image = NSImage(named: kImageStatusRunning)
        } else {
            startStopButton.state = NSOnState
            startStopButton.title = appDelegate.getStatusText()
            startStopButton.image = NSImage(named: kImageStatusStopped)
        }
    }

    override func mouseEntered(theEvent: NSEvent) {
        if appDelegate.isTimerRunning() {
            startStopButton.state = NSOffState
            startStopButton.title = kTextStatusPause
            startStopButton.image = NSImage(named: kImageStatusStopped)
        } else {
            startStopButton.state = NSOnState
            startStopButton.title = kTextStatusResume
            startStopButton.image = NSImage(named: kImageStatusRunning)
        }
    }

    override func mouseExited(theEvent: NSEvent) {
        handleButtonState()
    }

    @IBAction func startStopButtonClicked(sender: NSButton) {
        if appDelegate.isTimerRunning() {
            appDelegate.pauseTimer()
        } else {
            appDelegate.initializeTimer()
        }

        handleButtonState()
    }
}

Michael Samoylov

Python, JavaScript and Swift Expert with 12+ years of experience.

Vilnius, Lithuania https://monmar.tech

Subscribe to Michael Samoylov

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!