Dan Berry - Big Nerd Ranch Tue, 19 Oct 2021 17:47:08 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.5 Intro to NSWindow https://bignerdranch.com/blog/intro-to-nswindow/ https://bignerdranch.com/blog/intro-to-nswindow/#respond Mon, 25 Mar 2019 09:00:00 +0000 https://nerdranchighq.wpengine.com/blog/intro-to-nswindow/ NSWindow is an essential component in the macOS developer's toolkit. In this first part in a series on NSWindow, you'll learn how to create, size, and move windows.

The post Intro to NSWindow appeared first on Big Nerd Ranch.

]]>

So you’re ready to start writing your first macOS application. The project has been created in Xcode (Cocoa app, no storyboards, Swift), and sifting through the generated project structure you happen upon the AppDelegate file. This might be your first time writing a computer program, or perhaps this is your first on an Apple platform. You may even be giving macOS a look after years of iOS development. No matter what path you traveled to make it to this point, the following line in AppDelegate is our subject for today:

@IBOutlet weak var window: NSWindow!

On macOS, you need to have a good understanding of NSWindow, because you’ll be dealing with it quite a bit. NSWindow provides two basic functions: a screen area to add content such as views and controls, and a mechanism to relay input events. Event handling is a topic worthy of its own discussion, so for this article, we’re going to focus on creating, sizing, and moving windows.

We’re going to get started by creating a window in code, then exploring and adjusting from there. This may seem like a waste of time when we have Interface Builder available to us. It’s not. Interface Builder is a fabulous tool designed to save you time on repetitive tasks. However, there are two main drawbacks I find with it. First, it rarely provides access to all of the customization points available. Second, if the only way you’ve interacted with a class is through IB, you can potentially have a large gap in your understanding.

But enough about that, let’s get something up on the screen.

It Doesn’t Look Like Much

Before we write any code we have a small amount of cleanup to do. Start by removing the window IBOutlet in your app delegate, then delete the window object in MainMenu.xib. If you build and run now nothing will show onscreen. In applicationDidFinishLaunching, add the following code:

let contentRect = NSRect(x: 0, y: 0, width: 800, height: 600)
let window = NSWindow(contentRect: contentRect, styleMask: .borderless, backing: buffered, defer: false)
window.orderFront(nil)

Now when you run, you’ll see a rectangle showing up at the bottom left of your screen. I know, you probably expected a bit more, like rounded corners, the stop lights (close, minimize, and full screen), a shadow, etc. Let’s start by adding the stop lights. Just above our window initializer, define a styleMask local variable:

let styleMask: NSWindow.StyleMask = [.borderless, .closable, .miniaturizable, .resizable]

Use the new styleMask constant in the window initializer and run. Huh, still not what we expected. If we look at the docs for NSWindow.StyleMask, we have a clue as to why this might be. The .borderless option, “displays none of the usual peripheral elements”. In order to get the stop lights to show up the way we expect, we need to replace .borderless with .titled. This has the additional benefit of providing the other elements we expect for a window—the rounded corners, shadow, etc.

It’s a fair bet to say a good majority of the windows you encounter are of the titled variety. So why even have the borderless option? Well, if you want a window with a non-traditional shape, borderless would be the way to go since it’s the only way to truly get rid of the title bar. A more practical example would be the floating status messages you see when Xcode finishes building your project or running a test suite.

There’s one additional thing to be aware of after switching to a titled window. If the origin you provide for contentRect would result in the window being occluded by the dock, the window will be moved to compensate.

Next up, let’s take a look at the different options we have for sizing.

Size

There are two levels you can size windows at. The first is rather obvious—the window itself. The second is the content level. There are several properties with similar names between these two levels, and they are: aspect ratio, min and max size, and resize increments.

Of the available initializers for NSWindow, none of them allow you to set the frame of the window itself. Instead, you’ll be specifying either a rect, or a view controller for the content. This is a small detail and can cause unintended behavior in the future.

Let’s say your window needs to have the same aspect ratio as the screen. Easy enough, right? You can grab an instance of the main screen, and set the aspect ratio to the width and height of the screen. So before we order the window front add the following:

if let screen = NSScreen.main {
    window.aspectRatio = NSSize(width: screen.frame.width, height: screen.frame.height)
}

When you run the app you’ll see the same thing as before, which is to say not the aspect ratio we set on the window. You can confirm this by watching what happens when you start to resize the window. The window will first snap to the aspect ratio set earlier.

The initial reaction at this point might be to adjust the width and height passed to the initializer. Rather than go that route, let’s use the visual debugger with the code we already have. When you inspect the window you will see the height isn’t 600 like we specified. So how do you ensure the window size is what you expect before you display it? There are two ways to tackle this.

The first way would be to include one additional option in our styleMask: .fullSizeContentView. Going this route opts us in to layer-backing, and requires a little more when it comes to layout in the form of needing to use contentLayoutRect or contentLayoutGuide to ensure our content isn’t obscured by the title bar. The second option would be to set the frame of our window. This second approach will override whatever we passed in for contentRect. So which way should you go? I tend to go with setting the window’s frame, mainly because for simple operations I get animation for free by passing true to the animate parameter of the setFrame method.

There’s one last thing I want to cover about NSWindow right now—movement.

Movement

Moving a window comes mostly for free, but it’s still worth addressing, since there are a few points to consider. So let’s start by addressing what I mean by mostly. We’re going to head back to the beginning where we were creating a borderless window.

Out of the box windows are moved by clicking in the title bar and then dragging the window to the new destination. Since there is no title bar in a borderless window, you need to set the isMovableByWindowBackground property to true.

One last thing on the movement front. I’ve seen a lot of unnecessary window centering code over the years. Please make sure before you write code to center a window you look at the existing center() method. While the name isn’t truly representative of what it does, the docs are clear as to how it behaves and why, and well, it’s certainly easier to write than centerHorizontallyAndJustSlightlyHigherThanCenterVertically().

Ok, this wraps up the intro to NSWindow. There’s still all sorts of cool stuff to cover, but this is a good foundation to start with.

The post Intro to NSWindow appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/intro-to-nswindow/feed/ 0
Life in Technicolor https://bignerdranch.com/blog/life-in-technicolor/ https://bignerdranch.com/blog/life-in-technicolor/#respond Mon, 06 Aug 2018 10:00:00 +0000 https://nerdranchighq.wpengine.com/blog/life-in-technicolor/ Color is a fundamental and important aspect of app development. It helps identify brand, evokes emotion, and can even help us determine things like whether or not a button can be interacted with. See how to use the latest SDK enhancements to make working with color in your apps a breeze.

The post Life in Technicolor appeared first on Big Nerd Ranch.

]]>

Color is a fundamental and important aspect of app development. It identifies brand, evokes emotion, and teaches us things like whether a button can be touched. In the last few years alone, Apple has introduced multiple technologies making it worth revisiting how we approach the use of color in our apps. These are features like the True Tone display and P3 display gamut shipping in most Apple products, and interface styles (Light and Dark), available in tvOS and coming soon to macOS Mojave.

The 2017 SDK releases (iOS 11, macOS 10.13, tvOS 11, and watchOS 4) and Xcode 9 introduced the concept of named colors. Named colors are added to an asset catalog, and can then be used directly in Storyboards and Nibs, or in code using a new suite of initializer APIs. Let’s see how named colors make our lives easier, and also take a look at the enhancements coming in the 2018 SDKs.

The sample project for this article has examples of usage across all platforms, and any following code and screenshots will indicate which platform they are for.

We’ll start off with an empty iOS app, and add additional targets for tvOS and macOS. The next step will be to create a new asset catalog shared across all targets. We’ll call it Colors.

An empty asset catalog, which will be used specifically for colors.

With our colors asset catalog selected, select New Color Set from the Editor -> Add Assets menu item. You can change the name of this color over in the attributes inspector. Let’s name the first one Alizarin Crimson. I’ve chosen color names here familiar to a painter, but you can name a color whatever you want, perhaps to represent intention like “default button”. Since our colors will be shared across all platforms, and for consistency between them, it’s important to keep Universal checked under Devices, but you can always define colors per platform if your project needs call for it.

Now, select the image representation of our new color in the editor. The attributes inspector will update to include a new Color section. sRGB is selected for us by default, but you can also choose to use another gamut, or even choose a system color. The next item of interest is the input method. Do the designers on your project always provide colors in hex format? Well, you can change the input method to 8-bit Hexadecimal and input the value they sent you directly. Let’s do that here and enter #E32636.

Defining the Alizarin Crimson color in Xcode.

Next up, let’s say we want a more vibrant color on a P3 display. Let’s add a new color, and name it Cadmium Yellow. Define it in 8-bit (0-255) as Red 237, Green 135, and Blue 45. Now change Gamut from Any to sRGB and Display P3. A new color has been added in the editor below the original, and both currently reflect the color values we set earlier. Now, click on the Display P3 color. Change Content to Display P3. You’ll notice the color is still the same, but the values we supplied before are different. Change them back to what we had (237, 135, 45), and if you’re working on a P3 display you’ll see a more vibrant color than before.

Cadmium Yellow in wide gamut P3.

Following these same general steps, let’s create two more colors. For Phthalo Blue we’ll supply a High Contrast variant by checking the corresponding checkbox under Appearance. For Phthalo Green, we want to define a brighter color for Dark appearance. As was the case with cadmium yellow, these colors will change automatically given the right circumstances (e.g. switching between light and dark mode on Mojave).

The full list of our custom colors.

Great, so now we have four colors defined. How do we use them in our app?

We’ll start with examples on iOS and tvOS using Interface Builder. We add a few views, embed them in a stack view, set up our constraints, and then it’s as simple as changing the background color for each view. The selection interface even includes a dedicated section for all of your named colors!

Our named colors in Interface Builder.

It’s worth noting iOS and tvOS don’t support anything other than the display gamut at this time, but it may change at a later point.

We could achieve the same results we had on iOS and tvOS using NSBox on macOS, but if we use NSView instead, we can’t change the background color in IB. So let’s walk through how we would accomplish it in code. Here are the three code snippets that will get us up and running on macOS.

First, we create an enum so we don’t have to worry about typing our color names as raw strings everywhere.

enum PaintColors: String {
    case alizarinCrimson = “Alizarin Crimson”
    case cadmiumYellow = “Cadmium Yellow”
    case phthaloBlue = “Phthalo Blue”
    case phthaloGreen = “Phthalo Green”
}

Next we have a simple NSView subclass, which will fill itself with the set color.

class BackgroundColorView: NSView {

    var backgroundColor: NSColor?

    override func draw(_ dirtyRect: NSRect) {
        backgroundColor?.set()
        dirtyRect.fill()
    }

}

Finally, an NSWindow subclass contains outlets to four instances of our BackgroundColorView class, and the backgroundColor property is set on each of them.

class ColorWindow: NSWindow {

    @IBOutlet var alizarinCrimsonView: BackgroundColorView!
    @IBOutlet var cadmiumYellowView: BackgroundColorView!
    @IBOutlet var phthaloBlueView: BackgroundColorView!
    @IBOutlet var phthaloGreenView: BackgroundColorView!

    override func awakeFromNib() {
        super.awakeFromNib()

        alizarinCrimsonView.backgroundColor = NSColor(named: PaintColors.alizarinCrimson.rawValue)
        cadmiumYellowView.backgroundColor = NSColor(named: PaintColors.cadmiumYellow.rawValue)
        phthaloBlueView.backgroundColor = NSColor(named: PaintColors.phthaloBlue.rawValue)
        phthaloGreenView.backgroundColor = NSColor(named: PaintColors.phthaloGreen.rawValue)
    }

}

If you run this on a Mac running Xcode 10 and Mojave, you’ll have a new button in the debug area, which allows you to simulate different appearance combinations.

The new debug menu.

If you find yourself wanting to use named colors, but prefer not to use Interface Builder on iOS or tvOS, you should check out the following link:

init(named:) – UIColor | Apple Developer Documentation

There are a couple of gotchas worth mentioning when using named colors in your projects. First, how you define your colors matters. Using hexadecimal for sRGB and P3 will result in the same color, so you’ll want to use either the floating point or 8-bit (0-255) input methods.

Second, another option you have for adding colors via code is the use of color literals. You can do this either by showing the media library, then dragging and dropping the color into place in the editor, or by typing “Color Literal” and using autocomplete. This may seem like a great way to represent colors in code, but it’s important to understand this is not the same as using a named color. To see what I mean, open the file in an external editor. The Xcode editor builds the visual representation of the color for you, but the actual code is:
#colorLiteral(red: 0.8901960784, green: 0.1490196078, blue: 0.2117647059, alpha: 1)

If you decide to change the named color after the fact, or are trying to represent an appearance-dependent color in Mojave, you won’t get what you’re looking for.

The way we deal with colors on Apple platforms is evolving, which allows us to create more refined experiences. I encourage you to adopt the use of named colors in your apps!

The post Life in Technicolor appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/life-in-technicolor/feed/ 0