HOWTO: Create a Locked Down Fullscreen Cocoa Application and Implement NSLayoutConstraints using Swift


Why

We recently upgraded our kiosk software, which runs on a large screen in our foyer and allows visitors to explore Watershed’s wider programme such as upcoming cinema events, online creative work, and the Pervasive Media Studio.

rWeb

rWeb Interface

Our previous system used a series of scripts to suppress, disable and remove the dock and menu bar. Applications such as PowerPoint and Keynote automatically do this when they are put into presenter mode. Next time you run a powerpoint, move the mouse to the top or bottom of the screen – neither the Dock or the Menu Bar will appear. We needed to implement as similar system inside our Kiosk system rWeb.

In the past we have used Cocoa’s inbuilt suppression system which can be implemented in the Application’s Info.plist file. Simply create a new property and call it Application UI Presentation Mode, then set the value to 3. This will suppress the dock and menu bar, but briefly. We needed a more robust method of locking access to the dock and menu bar.

Here is our solution.

Solution

Create a new Cocoa Application in XCode selecting Swift as the language.

In your Main.storyboard file, expand the WindowController and click on the Window View. Then in the Utilities Inspector, open the Show the Attributes Inspector, Uncheck the Title Bar option as well as the Shadow option. Then change Full Screen Drop Down to Primary Window.

Attributes Inspector

Attributes Inspector

Add an NSImageView to the ViewController, don’t worry about creating a outlet for now, position it in the centre of your view. Drag and drop your own image or this one on to XCode’s project navigator, a window should appear. Make sure the Copy file if necessary option is checked.

Now add the image to the NSImageView. Simply click the ImageView, go to its Attributes Inspector and choose the image from the drop down selector. Also ensure that the image scaling is set to Proportionally Up or Down.

Then copy and paste the following code inside the ViewController Class in the ViewController.swift file.

//----------------------------------------------
    override func viewDidAppear() {
        let presOptions: NSApplicationPresentationOptions =
        //----------------------------------------------
        // These are all the options for the NSApplicationPresentationOptions
        // BEWARE!!!
        // Some of the Options may conflict with each other
        //----------------------------------------------

        //  .Default                   |
        //  .AutoHideDock              |   // Dock appears when moused to
        //  .AutoHideMenuBar           |   // Menu Bar appears when moused to
        //  .DisableForceQuit          |   // Cmd+Opt+Esc panel is disabled
        //  .DisableMenuBarTransparency|   // Menu Bar's transparent appearance is disabled
        //  .FullScreen                |   // Application is in fullscreen mode
            .HideDock                  |   // Dock is entirely unavailable. Spotlight menu is disabled.
            .HideMenuBar               |   // Menu Bar is Disabled
            .DisableAppleMenu          |   // All Apple menu items are disabled.
            .DisableProcessSwitching   |   // Cmd+Tab UI is disabled. All Exposé functionality is also disabled.
            .DisableSessionTermination |   // PowerKey panel and Restart/Shut Down/Log Out are disabled.
            .DisableHideApplication    |   // Application "Hide" menu item is disabled.
            .AutoHideToolbar

        let optionsDictionary = [NSFullScreenModeApplicationPresentationOptions :
            NSNumber(unsignedLong: presOptions.rawValue)]
    
        self.view.enterFullScreenMode(NSScreen.mainScreen()!, withOptions:optionsDictionary)
        self.view.wantsLayer = true
    }

When you run the application it should show your ImageView, take control of the screen and lock both the Dock and Menu Bar.

Constraints

Screen Shot 2015-06-03 at 12.07.01

Fullscreen

Now rather annoyingly the image stayed in the top left corner of the Window. Thats because we need to tell the NSImageView to align itself with the Centre of the Container/Super view.

We do this through Constraints. For those who might have not used NSLayoutConstraints before, they allow programmers to define extra layout parameters for User Interface Elements – in-spite of the view size or layout. For instance you are able to hardcode the maximum x distance between two separate buttons or how a label sits in relation to a WebView.

Despite their virtues though, Constraints can be temperamental and complicated tools for beginners. We will walk through how create some UI Elements that are constrained both programmatically and through XCode’s Interface Builder.

Lets constrain the image to the Centre of the view.

In Main.storyboard, click on the NSImageView. You’ll notice that in the bottom corner of the editor there are three small icons, click the first icon called Align. Check the Bottom two options which should be Horizontal Center in Container and Vertical Center in Container, set the constant value to 0. Then click Add Constraints.

Screen Shot 2015-06-03 at 12.09.49

Constrain the image to the Centre of the View

Next click on the Pin icon and check the Width and Height boxes define your size (I’ve just gone for 384×240). Add the Constraints.

Screen Shot 2015-06-03 at 12.42.02

Constrain the Image Size

Then re-run your application, the image should be in the centre of the main view.

Screen Shot 2015-06-03 at 12.43.21

New Image Size

Lets try adding some constraints programmatically.

  • First add two labels to the view putting them roughly central in the view.
  • Open the Assistant Editor (it looks like two rings overlapping on the Toolbar) and ensure that you can see the ViewController.swift file in the right hand side of the Window.
  • Now Right-Click the NSImageView and drag inside the ViewController Class.
  • Screen Shot 2015-06-03 at 12.49.12

    Create an @IBOutlet

  • Release the mouse button. A window will pop up, name your element imageView and click done. Do this for the labels as well naming textLabel and ScreenLabel.
  • In the viewDidLoad function, add the following code.
    var imageViewConstraints:[AnyObject] = [
                NSLayoutConstraint(item: self.imageView, attribute: .Width, relatedBy: .Equal, toItem: self.view, attribute: .Width, multiplier: 1.0, constant: -260),
                NSLayoutConstraint(item: self.imageView, attribute: .Height, relatedBy: .Equal, toItem: self.view, attribute: .Height, multiplier: 1.0, constant: -(83*2))]
    self.view.addConstraints(imageViewConstraints)
    

    This creates an array of Constraints, then adds array to the view. We’ll quickly explain the variables for the NSLayoutConstraint.

    • item: is the target element you want to constrain.
    • attribute: is the target element attribute you want to constrain.
    • relatedBy: is how you want to use the constant variable to constrain the element.
    • toItem is the secondary target element you want to constrain to, this is set to nil for individual constraints.
    • attribute: is the secondary target element attribute you want to constrain to.
    • multiplier: is the scaling value of the constant.
    • constant: is value you want to constrain by.
  • This is important: the earlier constraints we made to the Width and the Height of the NSImageView are still active and we are technically overwriting them. You need to remove the placeholder (or initial value). To do this highlight the constraints for NSImageView. In the Utilities Inspector check the Remove Constraint At Runtime
    Screen Shot 2015-06-03 at 14.27.44

    Remove Constraint At RunTime

  • Screen Shot 2015-06-03 at 14.23.41

    New Image

Finally let’s define some constraints for the labels in code.

  • Hightlight the two label elements and click the third icon constraint Resolve Auto Layout Issues
  • Select Add Missing Constraints
    Screen Shot 2015-06-03 at 14.32.42

    Add Missing Constraints

  • Like above we need to tell the compiler to ignore the constraints at Build Time. So highlight the new constraints and click Remove Constraint At Runtime.
    Screen Shot 2015-06-03 at 14.33.04

    Ignore Placeholder

  • In the ViewController.swift file add the following code to viewDidLoad
    var labelConstraints:[AnyObject] = [NSLayoutConstraint(item: self.textLabel, attribute: .CenterX, relatedBy: .Equal, toItem: self.view, attribute: .CenterX, multiplier: 1.0, constant: 0),
        NSLayoutConstraint(item: self.textLabel, attribute: .CenterY, relatedBy: .Equal, toItem: self.view, attribute: .Bottom, multiplier: 1.0, constant: -50)]
    
    var reportLabel:[AnyObject] = [NSLayoutConstraint(item: self.ScreenLabel, attribute: .CenterX, relatedBy: .Equal, toItem: self.view, attribute: .CenterX, multiplier: 1.0, constant: 0),
        NSLayoutConstraint(item: self.ScreenLabel, attribute: .Top, relatedBy: .Equal, toItem: self.textLabel, attribute: .Bottom, multiplier: 1.0, constant: 0)]
    
    self.view.addConstraints(labelConstraints)
    self.view.addConstraints(reportLabel)
    self.ScreenLabel.stringValue = "Screen Size Width: \(FirstScreenSize.width)  Height: \(FirstScreenSize.height)"

Re-run the application and the labels will align themselves according to your values.

Screen Shot 2015-06-03 at 14.55.48

Final Image

The source code for this project is available on Watershed’s GitHub page.

Similarly, the source code for rWeb is available here