Skip to content

Basics of 2D graphics


Prepared and tested with Xcode 9 and Swift 4

In this tutorial we cover the following topics

For more details please refer to Quartz 2D Programming Guide


General information


Core Graphics and AppKit

We have two ways to access the functionality made available by Core Graphics

  • We can use a wrapper named AppKit, which provide a simple and abstracted, but sometimes limited interface. This type of code is recognizable by the NS prefix (for example NSColor).
  • We can call the CG methods directly, leveraging all its power, but with the additional workload that that entails. A Core Graphics code is recognizable by the CG prefix (for example CGRect). As is generally the case with the core libraries, Core Graphics requires a bit more work from the developer but offers a greater degree of control and detail than is possible with AppKit's methods.


Quartz 2D

One of the main components of Core Graphics is a set of APIs called Quartz 2D. This is a collection of functions, data types, and objects designed to let us draw directly into a view or an image in memory. Quartz 2D treats the view or image that is being drawn into as a virtual canvas. It follows what’s called a painter’s model, which is just a fancy way of saying that the drawing commands are applied in much the same way that paint is applied to a canvas.

Quartz 2D provides a variety of line, shape, and image drawing functions. Though easy to use, Quartz 2D is limited to two-dimensional drawing.

When using Quartz 2D (Quartz for short), we will usually add the drawing code to the view doing the drawing. For example, we might create a subclass of NSView and add Quartz function calls to that class’s draw method. The draw method is part of the NSView class definition and is called every time a view needs to redraw itself.

Dependencies among all the components are shown below:

Todo (id=no todo id): img make a real image


Quartz 2D’s Graphics Contexts

In Quartz, as in many other graphics libraries, drawing happens in a graphics context, usually referred to simply as a context. Every view has an associated context. We retrieve the current context and use it to make various Quartz drawing calls, and let the context worry about rendering our drawing onto the view. We can think of this context as a sort of canvas. The system provides us with a default context where the contents will appear on the screen. However, it’s also possible to create a context of our own for doing drawing that we don’t want to appear immediately, but to save for later or use for something else. We’re going to be focusing mainly on the default context, which we can acquire with this line of code:

A graphics context is an opaque data type (CGContext) that encapsulates all the information Quartz uses to draw images to an output device, such as a PDF file, a bitmap, or a window on a display. The information inside a graphics context includes graphics drawing parameters and a device-specific representation of the paint on the page. All objects in Quartz are drawn to, or contained by, a graphics context.

Thanks to graphics context, when we draw with Quartz, all device-specific characteristics are contained within the specific type of graphics context we use. In other words, we can draw the same image to a different device simply by providing a different graphics context to the same sequence of Quartz drawing routines. We do not need to perform any device-specific calculations; Quartz does it for us.


Example 1: use AppKit to create custom button

  1. Step 1: setting up a project in Xcode
    Launch Xcode and select File | New | Project... from main menu or from Welcome to Xcode screen select Create a new Xcode project.

    In project window select macOS | Application | Cocoa App template and then click the Next button.

    You will see the project options sheet, which should look like below (image below shows the completed options sheet after specifying the fields)

    Press Next button, and you will be prompted for a location for your project. When selected, press Create to save the project.
  2. Step 2: add Custom View
    Single click Main.storyboard file to open the application’s view in Xcode’s Interface Builder.

    Drag a Custom View object onto the View Controller Scene

    Select the Size inspector by selecting right icon (or press command + option + 5) and set its Width and Height property to 101.
  3. Step 3: create a stub of Custom View's class
    Select File | New | File... to create a new Cocoa class file under the name CustomButtonRoadSign, and make it a subclass of NSButton

    Select Main.storyboard, click on Cutom View we have previously add. Next select the Identity Inspector by selecting right icon (or press command + option + 3) and set its Custom Class property to CustomButtonRoadSign.
  4. Step 4: add basic test code
    Although not necessary it would be convenience to see at ones application’s view in Xcode’s Interface Builder as well as content of CustomButtonRoadSign.swift file.

    Add the following code to the CustomButtonRoadSign.swift file:


    Live rendering with @IBDesignable and @IBInspectable.

    Xcode has an great feature: live rendering. It allows us to see how our custom view looks in Interface Builder without the need to build and run our application.
    To enable it, we just need to add the @IBDesignable annotation to our class (and optionally implement prepareForInterfaceBuilder() method to provide some sample data).

    We can also change the visual attributes of our custom view in real time inside Interface Builder. To to that we just need to add the @IBInspectable annotation to a property. Note that to make a property inspectable, we must declare its type, even if it’s obvious from the rest of our code.

    To verify if live rendering works replace the // Drawing code here. line located in CustomButtonRoadSign class's draw(_ dirtyRect: NSRect) method with:

    You may wander why we use a Custom View object while CustomButtonRoadSign inherits from an NSButton. The reason for this is that an NSButton doesn't render in live view in Interface Builder.

  5. Step 5: prepare to make a target view
    Add / modify CustomButtonRoadSign class with the following code


    As a result we should be able to see thin yellow frame around red rectangle. In fact there are two rectangles: bigger which is yellow and is drawn as a firsst and a little bit smaler which is red and drawn over the first one.
  6. Step 6: draw target view
    Add / modify CustomButtonRoadSign class with the following code


    As we can see I have changed border line thickness which was for me to thick, removed yellow square and chenge red color to blue.
  7. Step 7: keep target drawing area square
    Add / modify CustomButtonRoadSign class with the following code


    As we can see a drawing area is keept square.
  8. Step 8: draw an arrow
    Add / modify CustomButtonRoadSign class with the following code

  9. Step 9: add stop
    Add / modify CustomButtonRoadSign class with the following code


    Remember to change Normal property in the Attributes inspector from On/Default to Off to be able to see second version of our button.
  10. Step 10: test
    As we know, an NSButton doesn't render in live view in Interface Builder. So to test we have to go back to NSButton class.

    You can keep or delete the NSView, and drag an NSButton (Push button) from the object library. In the Attributes Inspector, set the button's Style property to Square, so that it becomes fully resizable, and deselect Bordered. You may also want to delete Title.


    Next in the Identity Inspector set button's Custom Class property to CustomButtonRoadSign.
    In Size insoector set the button size.
    Add / modify CustomButtonRoadSign class with the following code

    Run our application. Now we should be able to click on the button to check that it shows its highlighted state (stop sign should change to blue sign).

  11. Step 11: fix
    In my case button's coordinate system occured to be flipped. By default, Cocoa uses a standard Cartesian coordinate system, where positive values extend up and to the right of the origin and the origin itself is positioned in the bottom-left corner of the current view or window. When flipped, positive values extend down and to the right of the origin and the origin itself is positioned in the top-left corner of the current view or window (see Flipped Coordinate Systems section in https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Transforms/Transforms.html)

    Add / modify CustomButtonRoadSign class with the following code

    Now the arrow should point up.


Example 2: use Core Graphics to create custom view

  1. Step 1: setting up a project in Xcode
    Launch Xcode and select File | New | Project... from main menu or from Welcome to Xcode screen select Create a new Xcode project. Next,
    in project window select macOS | Application | Cocoa App template and then click the Next button.

    You will see the project options sheet, which should look like below (image below shows the completed options sheet after specifying the fields)

    Press Next button, and you will be prompted for a location for your project. When selected, press Create to save the project.
  2. Step 2: add Custom View
    Single click Main.storyboard file to open the application’s view in Xcode’s Interface Builder.

    Drag a Custom View object onto the View Controller Scene

    Select the Size inspector by selecting right icon (or press command + option + 5) and set its Width and Height property to 101.
  3. Step 3: create a stub of Custom View's class
    Select File | New | File... to create a new Cocoa class file under the name CustomViewTrafficLight, and make it a subclass of NSView

    Select Main.storyboard, click on Cutom View we have previously add. Next select the Identity Inspector by selecting right icon (or press command + option + 3) and set its Custom Class property to CustomViewTrafficLight.
  4. Step 4: add basic test code
    Although not necessary it would be convenience to see at ones application’s view in Xcode’s Interface Builder as well as content of CustomViewTrafficLight.swift file.

    Replace the // Drawing code here. line located in CustomViewTrafficLight class's draw(_ dirtyRect: NSRect) method with:

    Remember to add @IBDesignable just before the class to enable live rendering

  5. Step 5: target view: first approach (flat)
    Add / modify CustomViewTrafficLight class with the following code

  6. Step 6: target view: second approach (gradient)
    Add / modify CustomViewTrafficLight class with the following code

  7. Step 7: target view: third approach (border with a gradient)
    Add / modify CustomViewTrafficLight class with the following code