Shaking a macOS Window

There may come a time when you’d like to shake a macOS application’s window (or one of them) to reinforce an event. In my case, I am creating a macOS application that serves as a conduit to control aspects of a user experience. It communicates with an iOS application which serves as a kind of remote control for the macOS application. And settings are synchronized back and forth. It’s pretty cherry.

macOS Sierra and iOS 10 using Swift 3 are quite similar. I know enough about iOS development to make myself a drunken lumberjack in a glade of birch trees – swinging wildly hoping to hit things. Anyway, I am using MultipeerConnectivity framework for the communication discovery and data transfer (at a top-level explanation). When a connection is made (the iOS app advertises), I turn a little grey dot into a green one to show this state. When a disconnection is made, I turn it back to grey.

However, this might not be enough. I could play a disconnection tone (and I still might), but I wanted to shake the window to let someone know, “Hey! I lost a connection.” This sets expectations and it’s a cool effect I wanted to try out.

I have an extension that will do this, written for Swift 3. It’s implementation is a little different than for previous versions of Swift. Here is the extension. The Cocoa import is because I keep separate extension swift files instead of in-lining everything into a view controller, etc. This keeps things cleaner and a little easier for me and others to find.

import Cocoa

/**
 
 window?.shakeWindow()
 view.window?.shakeWindow()
 NSApp.windows.first?.shakeWindow()
 
 */

extension NSWindow {
    func shakeWindow(){
        let numberOfShakes          = 3
        let durationOfShake         = 0.3
        let vigourOfShake : CGFloat = 0.04
        let frame : CGRect = self.frame
        let shakeAnimation :CAKeyframeAnimation  = CAKeyframeAnimation()
        
        let shakePath = CGMutablePath()
        shakePath.move( to: CGPoint(x:NSMinX(frame), y:NSMinY(frame)))
        
        for _ in 0...numberOfShakes-1 {
            shakePath.addLine(to: CGPoint(x:NSMinX(frame) - frame.size.width * vigourOfShake, y:NSMinY(frame)))
            shakePath.addLine(to: CGPoint(x:NSMinX(frame) + frame.size.width * vigourOfShake, y:NSMinY(frame)))
        }
        
        shakePath.closeSubpath()
        shakeAnimation.path = shakePath
        shakeAnimation.duration = durationOfShake
        
        self.animations = ["frameOrigin":shakeAnimation]
        self.animator().setFrameOrigin(self.frame.origin)
    }
}

I certainly hope that you enjoy it.

Leave a Reply

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload CAPTCHA.

This site uses Akismet to reduce spam. Learn how your comment data is processed.