This article is translated from Raywenderlich.com’s Core Graphics on MacOS Tutorial. You can translate up to 10 articles in this Tutorial. I hope that if you have English reading ability, you’d better give credit first and then read the English original. After all, no matter Xcode, or official documents, or all kinds of cutting-edge information is only in English version. To sum up, this translation version is for reference only, declined to reprint.

You are also welcome to click on my avatar to see my other MacOS development tutorials translated?

Updated on 22nd September 2016: This tutorial has been updated to Xcode 8 and Swift 3.

You must have seen many apps with beautiful interfaces and gorgeous custom views, they must have left a deep impression in your heart, because they are that! Yao! Good! Look!

Core Graphics is the 2D Graphics engine provided by Apple, and it’s probably the coolest of all MacOS and iOS frameworks. It can be used to draw all kinds of graphics you can think of, from simple geometric shapes to complex visual effects like shadows and gradients.

In this MacOS Core Graphics tutorial, you’ll create a custom view called DiskInfo, which uses a pie chart and a bar chart to show the hard drive space available on your Mac. This tutorial will give you the ability to take a boring UI and turn it into something exciting:

In this tutorial you will learn:

  • Create and configure a custom view, which is necessary to draw graphical elements.
  • Real-time Render Preview allows you to see your changes in the Interface Builder without compiling and running them.
  • Use code to draw paths, populate graphics, create clipping mask clips, and render text;
  • Use the advanced classes and methods provided by the Cocoa Drawing tools in AppKit.

In Part 1, you will implement Drawing a pie chart using Core Graphics, and later you will learn how to achieve the same effect using Cocoa Drawing.

So grab your paintbrush and we’re going to paint

Ready to start

Click here to download the DiskInfo starter project, compile and run it:

This app will list all your hard drives and click on any one to see their details.

Before we start, let’s familiarize ourselves with the structure of the project:

  • Viewcontroller.swift: The main ViewController of the app;
  • VolumeInfo.swift: Realized for processing hard disk informationVolumeInfoClass, and for analyzing the space taken up by different file typesFilesDistributionStructure;
  • NSScolor +DiskInfo.swift and NSFont+DiskInfo.swift: Extends the NSScolor to define the colors and fonts to be used in the app.
  • CGFloat+ radians. swift: extends CGFloat to provide helper methods for converting Angle values and Radians;
  • MountedVolumesDataSource. Swift and MountedVolumesDelegate. Swift: to realize the hard disk information necessary for all kinds of methods.

Note: This app can display your actual hard drive usage, but in this tutorial, it will generate random data. Calculating the types of all the files on your hard drive every time you launch your app is time-consuming and takes all the fun out of it. Nobody wants to waste their time doing that, right? ?

Create a custom view

The first thing you need to do is create a custom view called GraphView. This will be where you draw the pie chart and bar chart. In this section you need to do two things:

  1. To create aNSViewThe subclass.
  2. rewritedraw(_:)Method, and add some code to draw.

Create a subclass of NSView

Select the Views group in the project navigator and go to File → New → File… Then click MacOS → Source → Cocoa Class Templates.

Click Next, name the new class GraphView and let it inherit from NSView, and select the language as Swift.

Click Next and Create to save your file.

Open Main.storyboard, in the View Controller Scene, and drag in a Custom View from the control library.

Select the Custom View, in the identity inspector, and set its class name to GraphView.

You need some constraints, so keep the Graph View selected and click the Pin button on the Auto Layout toolbar to set its Top, Bottom, Leading and Trailed constraints to 0. Then click the Add 4 Constrains button.

Click the Resolve Auto Layout Issues button in the triangle on the Auto Layout toolbar, and then click Update Frames in the Selected Views section. If this option is not available, you may need to deselect Graph View by clicking in the margin, and then select it again.

rewritedraw(_:)

Open GraphView.swift and you can see that Xcode has automatically created an implementation of draw(_:) for us. Replace that comment with the following code, and make sure you don’t accidentally delete the line on which it calls the parent method.

NSColor.white.setFill() 
NSRectFill(bounds)

The first line sets the fill color to white, and then by calling the NSRectFill method, you set the entire view’s background to white.

Compile and run:

You have changed the background of the custom view from the default grey to white.

Haha, our canvas is ready! It’s that simple

Live Render Preview:@IBDesignable@IBInspectable

Xcode 6 brings us one great feature: real-time render preview. It allows you to see what your custom view looks like in Interface Builder without having to compile and run it every time.

To enable this feature, you need to decorate your class with @IBDesignable; And implement prepareForInterfaceBuilder () method to provide some sample data (realization of this method is not a must).

Open GraphView.swift and add the following before the class definition:

@IBDesignable

Now you need to add this code to the GraphView class by providing some sample data:

var fileDistribution: FilesDistribution? { didSet { needsDisplay = true } } override func prepareForInterfaceBuilder() { let used = Int64(100000000000) let available = used / 3 let filesBytes = used / 5 let distribution: [FileType] = [ .apps(bytes: filesBytes / 2, percent: 0.1),.photos(bytes: FilesBytes, Percent: 0.2),.movies(bytes: FilesBytes * 2, Percent: 0.15),.audio(bytes: FileBytes, Percent: 0.18),.other(bytes: filesBytes, Percent: 0.2)] fileDistribution = FileDistribution (capacity: used + available, available: available, distribution: distribution) }

This will define a FileDistribution property to store information about the hard drive. When this property changes, it sets the view’s NeedsDisplay property to true, allowing the view to redraw its own content.

And then, it implements the prepareForInterfaceBuilder () method, in order to create a variety of examples of file type, used to Xcode preview this view.

Note: You can even change visual properties in real time in Interface Builder. This requires you to decorate the property with @ibinspectable.

Next: decorate all the visual attributes with @ibInspectable and add this code to the GraphView declaration:

// 1 FILErivate struct Constants {static let barminHeight: CGFloat = 30.0 static let barminHeight: CGFloat = 20.0 static let barmaxHeight: CGFloat = 40.0 static let marginSize: marginSize: CgFloat = 20.0 static let PieChartwidthpercentage: CgFloat = 1.0/3.0 static let PieChartBorderWidth: CGFloat = 1.0 static let PieChartMinRadius: CGFloat = 30.0 static let PieChartGradientAngle: CGFloat = 90.0 static let barChartCornerRadius: CGFloat = 4.0 static let barChartLegendSquareSize: CgFloat = 8.0 static let LegendTextMargin: CgFloat = 5.0} // 2 @ibInspectable var barHeight: CGFloat = Constants.barHeight { didSet { barHeight = max(min(barHeight, Constants.barMaxHeight), Constants.barMinHeight) } } @IBInspectable var pieChartUsedLineColor: NSColor = NSColor.pieChartUsedStrokeColor @IBInspectable var pieChartAvailableLineColor: NSColor = NSColor.pieChartAvailableStrokeColor @IBInspectable var pieChartAvailableFillColor: NSColor = NSColor.pieChartAvailableFillColor @IBInspectable var pieChartGradientStartColor: NSColor = NSColor.pieChartGradientStartColor @IBInspectable var pieChartGradientEndColor: NSColor = NSColor.pieChartGradientEndColor @IBInspectable var barChartAvailableLineColor: NSColor = NSColor.availableStrokeColor @IBInspectable var barChartAvailableFillColor: NSColor = NSColor.availableFillColor @IBInspectable var barChartAppsLineColor: NSColor = NSColor.appsStrokeColor @IBInspectable var barChartAppsFillColor: NSColor = NSColor.appsFillColor @IBInspectable var barChartMoviesLineColor: NSColor = NSColor.moviesStrokeColor @IBInspectable var barChartMoviesFillColor: NSColor = NSColor.moviesFillColor @IBInspectable var barChartPhotosLineColor: NSColor = NSColor.photosStrokeColor @IBInspectable var barChartPhotosFillColor: NSColor = NSColor.photosFillColor @IBInspectable var barChartAudioLineColor: NSColor = NSColor.audioStrokeColor @IBInspectable var barChartAudioFillColor: NSColor = NSColor.audioFillColor @IBInspectable var barChartOthersLineColor: NSColor = NSColor.othersStrokeColor @IBInspectable var barChartOthersFillColor: NSColor = NSColor.othersFillColor // 3 func colorsForFileType(fileType: FileType) -> (strokeColor: NSColor, fillColor: NSColor) { switch fileType { case .audio(_, _): return (strokeColor: barChartAudioLineColor, fillColor: barChartAudioFillColor) case .movies(_, _): return (strokeColor: barChartMoviesLineColor, fillColor: barChartMoviesFillColor) case .photos(_, _): return (strokeColor: barChartPhotosLineColor, fillColor: barChartPhotosFillColor) case .apps(_, _): return (strokeColor: barChartAppsLineColor, fillColor: barChartAppsFillColor) case .other(_, _): return (strokeColor: barChartOthersLineColor, fillColor: barChartOthersFillColor) } }

What this blob of code does is:

  1. Declares structures with lots of constants — you have to use them everywhere in the app;
  2. with@IBInspectableModify all configurable properties. And useNSColor+DiskInfo.swiftAssigns a value to them. Note: To make a property “inspectable” (which can be edited directly in Interface Builder), you must declare its type, even though Swift will do this for you in most cases;
  3. Define a Helper method that returns the stroke color and fill color for a different file type. You’ll use it when you’re charting what files take up how much space.

Open Main.stroyboard and you should notice that the Graph View has changed from its default gray to white, which means the Live Rrender Preview is in action.

Select Graph View, open the property inspector, and you will see that all the “inspectable” properties are already there.

To see how it works, you can either compile it and run it, or you can view it in the Interface Builder.

Everything is ready, it’s time to start the real painting!

Graphics Context = Graphics Context

When you use Core Graphics, you don’t draw directly in the view. Instead, you use something called the Graphics Context, which is the middle layer between the system rendering the Graphics and displaying them in the view.

Core Graphics uses a mode called “Painter’s Model,” where you can imagine yourself taking a pen and swishingly drawing on a canvas. You need to place a path and then fill it. You can’t change the pixels that are already there, but you can continue to draw on top of them.

This “context” is important because it determines the effect you end up with.

Draw the path

To draw a Path using Core Graphics, you need to define a PATH, which is CGPathRef and the mutable CGMutablePathRef.

Once the path is ready, add it to the graphics context and you can render your graphics based on the path and the drawing properties.

As the pie chart… Draw a Path

The basic element of a bar chart is a rounded rectangle, so let’s start there.

Open GraphView.swift and add the extension at the bottom of the file, somewhere outside the class definition:

// MARK: - func DrawRect (rect: CGRect, inContext context: CGContext?) {func DrawRect (rect: CGRect, inContext context: CGContext? , radius: CGFloat, borderColor: CGColor, fillColor: CGColor) { // 1 let path = CGMutablePath() // 2 path.move( to: CGPoint(x: rect.midX, y:rect.minY )) path.addArc( tangent1End: CGPoint(x: rect.maxX, y: rect.minY ), tangent2End: CGPoint(x: rect.maxX, y: rect.maxY), radius: radius) path.addArc( tangent1End: CGPoint(x: rect.maxX, y: rect.maxY ), tangent2End: CGPoint(x: rect.minX, y: rect.maxY), radius: radius) path.addArc( tangent1End: CGPoint(x: rect.minX, y: rect.maxY ), tangent2End: CGPoint(x: rect.minX, y: rect.minY), radius: radius) path.addArc( tangent1End: CGPoint(x: rect.minX, y: rect.minY ), tangent2End: CGPoint(x: rect.maxX, y: rect.minY), radius: radius) path.closeSubpath() // 3 context? . SetLineWidth (1.0) the context? .setFillColor(fillColor) context? .setStrokeColor(borderColor) // 4 context? .addPath(path) context? .drawPath(using: .fillStroke) } }

Too long to see? ️ : That’s how you draw a rounded rectangle, in human language:

  1. Create a path that can be changed;
  2. Step by step outline a rounded rectangle:

    • Let’s go to the midpoint of the bottom of the rectangle, which is going to be our starting point;
    • useaddArc(tangent1End:tangent2End:radius)Method draws a line segment in the lower right corner. This method draws a horizontal line at the bottom and a rounded corner in the lower right corner.
    • Add the right line segment and the rounded corner in the upper right corner;
    • Add top line segment and top left corner;
    • Add the left line segment and the rounded corner in the lower left corner;
    • A closed path, which is a connection from the point in the previous step to the starting point;
  3. Set the drawing properties: line width, fill color, and border color;
  4. Add the path to the graphics context and use.fillStrokeThe parameter draws a path. This parameter tells Core Graphics that the path needs to be filled with color and a border drawn.

Calculation of position

The process of drawing with Core Graphics is essentially the process of calculating the position of each visual element in the view. So all we need to care about is where we put the different elements and how they react when the size of the view changes.

We are going to layout our view like this:

Open GraphView.swift and add this extension:

// MARK: - Extension Extension GraphView {// 1 Func PiechartRectangle () - BB0 CRECT {let width = bounds.size.width * Constants.pieChartWidthPercentage - 2 * Constants.marginSize let height = bounds.size.height - 2 * Constants.marginSize let diameter = max(min(width, height), Constants.pieChartMinRadius) let rect = CGRect(x: Constants.marginSize, y: Bounds. MIDI-diameter / 2.0, width: diameter, height: diameter) return rect } // 2 func barChartRectangle() -> CGRect { let pieChartRect = pieChartRectangle() let width = bounds.size.width - pieChartRect.maxX - 2 * Constants.marginSize let rect = CGRect(x: pieChartRect.maxX + Constants.marginSize, y: pieChartRect.midY + Constants.marginSize, width: width, height: barHeight) return rect } // 3 func barChartLegendRectangle() -> CGRect { let barchartRect = barChartRectangle() let rect = barchartRect.offsetBy(dx: 0.0, dy: -(barchartRect.size.height + Constants. MarginSize)) return rect}}

The code above does the necessary calculations:

  1. Start by calculating the position of the pie chart, which will be vertically centered and occupy 1/3 of the width of the view.
  2. Calculate the position of the bar chart. It will occupy 2/3 of the width and be slightly above the middle of the view.
  3. Calculate the position of the legend based on the minimum Y value and margin of the pie chart.

Now let’s draw it into your view by adding in the GraphView extension for drawing:

func drawBarGraphInContext(context: CGContext?) {
  let barChartRect = barChartRectangle()
  drawRoundedRect(rect: barChartRect, inContext: context,
                  radius: Constants.barChartCornerRadius,
                  borderColor: barChartAvailableLineColor.cgColor,
                  fillColor: barChartAvailableFillColor.cgColor)
}

You need a Helper method to draw a bar chart, which will draw a rounded rectangle and use the brush color and fill color to draw the figure in the margin. You can find these colors in the NSColor+DiskInfo extension.

Replace all the code in the draw(_:) method with:

super.draw(dirtyRect) let context = NSGraphicsContext.current()? .cgContext drawBarGraphInContext(context: context)

This code will actually draw the graph into the view. First of all, by calling the NSGraphicsContext. Current (), we obtain the current view of the graphics context, and then we call just writing method to draw the bar chart.

Compile and run it, and you can see the bar graph in place:

Now, open Main.storyboard and select the View Controller Scene and you should see this:

Interface Builder renders the preview for you in real time. You can try to change the color and it will respond to your changes in real time. Isn’t that cool? ~

Cut out part of the area (i.e. Mask)

Now let’s make a file distribution, which is this guy:

Pause for a second and let’s get our heads together. Obviously, each file has its own unique color, so our app simply calculates the width of each square based on the percentage of the disk space occupied by the file and draws it in the corresponding color.

You need to draw an irregular shape, like one! @ # selections % %. However, we can avoid writing duplicate code by using a technique called clipping areas.

? The first sentence of this paragraph is really not understood. You could create a special shape, such as a filled rectangle with two lines at the bottom and top of the rectangle.

You can think of a mask as “cutting a hole in a piece of paper” through which you can only see part of the image. This “hole” is called a “Clipping Mask,” and you’ll need to define it in Core Graphics.

In the bar chart example, you need to create a full rounded rectangle for each file category and then use a clipping mask to make them show only the correct parts:

That’s the theory, let’s do it

Before spending is plotted, you need to set up FileDistribution for the selected hard drive. Open main.storyboard, and we’re going to create an Outlet connection.

Hold down the Option⌥ key in the project navigator and click ViewController.swift to make it appear in the right side of the assistance editor. Then hold down the Control key and drag the Graph View into the ViewController code.

In the little bubble that pops up, call this Outlet GraphView, and click Connect.

Open ViewController.swift and add this line of code to the end of the showVolumeInfo(_:) :

graphView.fileDistribution = volume.fileDistribution

This line of code sets the value of FileDistribution so that the Graph View can get the percentage of each type of file.

Open GraphView.swift and add this code to the end of the drawBarGraphinContext (context:) to draw the bar graph:

// 1 if let fileTypes = fileDistribution? .distribution, let capacity = fileDistribution? .capacity, capacity > 0 { var clipRect = barChartRect // 2 for (index, fileType) in fileTypes.enumerated() { // 3 let fileTypeInfo = fileType.fileTypeInfo let clipWidth = floor(barChartRect.width * CGFloat(fileTypeInfo.percent)) clipRect.size.width = clipWidth // 4 context? .saveGState() context? .clip(to: clipRect) let fileTypeColors = colorsForFileType(fileType: fileType) drawRoundedRect(rect: barChartRect, inContext: context, radius: Constants.barChartCornerRadius, borderColor: fileTypeColors.strokeColor.cgColor, fillColor: fileTypeColors.fillColor.cgColor) context? .restoreGState() // 5 clipRect.origin.x = clipRect.maxX } }

This code does the following:

  1. Make sure that the Graph View has a valid onefileDistribution;
  2. traversefileDistributionEvery file type in the
  3. Calculate the size of the mask according to the proportion of the file;
  4. Store the state of the graphics context, set the size of the mask, draw the rounded rectangle with the corresponding color of the file type, and restore the state of the graphics context;
  5. Move the x of the clipping mask to the correct position.

You may be wondering: why would you want to save the graphics context first and then restore it? Remember Painter’s Model? Everything you add to the context of a graphic will be kept in the context, just like a painting you draw on paper, it will always be there.

If you add multiple clipping masks, you are actually creating one clipping mask and applying it to all the rectangles. To avoid this, you need to store the state of the context before adding a new clipping mask, restore it when you’re done with the mask, and then process the new mask.

At this point, Xcode will pop up a warning because Index has never been used. Don’t worry, it’ll come in handy later.

Compile and run, or just open main. storyboard:

Haha, the DiskInfo feature seems to be getting better and better. Except for the legend, the bar chart is almost complete. .

Draw text

Drawing text in a custom view is particularly easy. You need to create a dictionary for the various attributes of the text, including font, size, color, and alignment, and pass it to the String’s draw(in:withAttributes:) method. These properties will come in handy when we calculate the size and position of the rectangle.

Open GraphView.swift and add this property to the class definition:

fileprivate var bytesFormatter = ByteCountFormatter()

This will create a BytecountFormatter. It will help us with the tedious task of turning bytes into human words.

Now, add this code to the for (index,fileType) in filetype.enumerated () loop of the drawBarGraphInContext(context:) method:

// 1 let legendRectWidth = (barChartRect.size.width / CGFloat(fileTypes.count)) let legendOriginX = barChartRect.origin.x + floor(CGFloat(index) * legendRectWidth) let legendOriginY = barChartRect.minY - 2 * Constants.marginSize let legendSquareRect = CGRect(x: legendOriginX, y: legendOriginY, width: Constants.barChartLegendSquareSize, height: Constants.barChartLegendSquareSize) let legendSquarePath = CGMutablePath() legendSquarePath.addRect( legendSquareRect ) context? .addPath(legendSquarePath) context? .setFillColor(fileTypeColors.fillColor.cgColor) context? .setStrokeColor(fileTypeColors.strokeColor.cgColor) context? .drawPath(using: .fillStroke) // 2 let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineBreakMode = .byTruncatingTail paragraphStyle.alignment = .left let nameTextAttributes = [ NSFontAttributeName: NSFont.barChartLegendNameFont, NSParagraphStyleAttributeName: paragraphStyle] // 3 let nameTextSize = fileType.name.size(withAttributes: nameTextAttributes) let legendTextOriginX = legendSquareRect.maxX + Constants.legendTextMargin let legendTextOriginY = legendOriginY - 2 * Constants.pieChartBorderWidth let legendNameRect = CGRect(x: legendTextOriginX, y: legendTextOriginY, width: legendRectWidth - legendSquareRect.size.width - 2 * Constants.legendTextMargin, height: nameTextSize.height) // 4 fileType.name.draw(in: legendNameRect, withAttributes: nameTextAttributes) // 5 let bytesText = bytesFormatter.string(fromByteCount: fileTypeInfo.bytes) let bytesTextAttributes = [ NSFontAttributeName: NSFont.barChartLegendSizeTextFont, NSParagraphStyleAttributeName: paragraphStyle, NSForegroundColorAttributeName: NSColor.secondaryLabelColor] let bytesTextSize = bytesText.size(withAttributes: ByTestExtAttributes) let byTestExtRect = legendNameRect. OffsetBy (dx: 0.0, dy: -byTestExtSize.height) byTestExt.draw (in: bytesTextRect, withAttributes: bytesTextAttributes)

This pile of code looks pretty deceptive, but it’s actually pretty simple:

  1. You’re already familiar with this code: Calculate the location of the legend’s colored square, create a path for it, and fill it with the corresponding color;
  2. Create a dictionary with two attributes: font andNSMutableParagraphStyle. The latter defines how the text will be drawn within a given rectangle. In this example, the text will be left aligned, and if the text goes outside the rectangle, the ellipsis will be added to the end.
  3. Calculates the position and size of the rectangle used to draw text;
  4. calldraw(in:withAttributes:), draw the text;
  5. usebytesFormatterGets the text and sets the properties of the “File Size” text. The only difference is that this text usesNSFontAttributeNameSet a different color.

Compile and run, or go to main.storyboard.

Congratulations on the successful completion of the bar chart! You can now resize the window and see how the text in the legend uses ellipses to fit the tight space.

Give yourself a hand? ~

Cocoa Drawing

MacOS also provides the option to draw using AppKit’s framework. It will provide a higher level of abstract drawing. It uses various classes in place of C functions, and it includes a number of Helper methods to make common drawing tasks easier. The graphical context is the same in both frameworks. If you are familiar with Core Graphics, you should be able to master Cocoa Drawing easily.

Like Core Graphics, you need to create and draw paths, but in Cocoa Drawing, we use NSBezierPath, which is the same as CGPathRef.

The pie chart we want to draw looks like this:

You need to draw it in three steps:

  1. Create a circular path to display the total disk space and draw it with the defined color.
  2. Create a path for the used space and draw it.
  3. Draws a gradient fill for the path of the used space.

Open GraphView.swift and add this method to the extension used for drawing:

func drawPieChart() { guard let fileDistribution = fileDistribution else { return } // 1 let rect = pieChartRectangle() let circle = NSBezierPath(ovalIn: rect) pieChartAvailableFillColor.setFill() pieChartAvailableLineColor.setStroke() circle.stroke() circle.fill() // 2 let  path = NSBezierPath() let center = CGPoint(x: rect.midX, y: rect.midY) let usedPercent = Double(fileDistribution.capacity - fileDistribution.available) / Double (fileDistribution capacity) let endAngle = CGFloat (360 * usedPercent) let the radius = the rect. Size, width / 2.0 path.move(to: center) path.line(to: CGPoint(x: rect.maxX, y: center.y)) path.appendArc(withCenter: center, radius: radius, startAngle: 0, endAngle: endAngle) path.close() // 3 pieChartUsedLineColor.setStroke() path.stroke() }

Let’s examine this code:

  1. withinit(ovalIn:)The constructor creates a circular path, sets its fill color and stroke color, and then draws the path.
  2. Create a path for the used space:

    • Calculate the Angle of the sector according to the space used;
    • Move to the center of the great circle;
    • Add a line segment from the center to the right vertex of the circle.
    • Add an arc based on the previously calculated Angle;
    • Closed figure, that is, from the end of the arc to the center of the circle;
  3. callstroke()Method, sets the stroke color;

You should notice the difference between this code and the previous one:

  • The graphic context is not mentioned in the code, because the method we call automatically gets the current context, in this case, the view’s own graphic context;
  • Angles are measured in degrees, not radians. CGFloat+Radians.swift extends the CGFloat class for automatic conversion.

Now add this line to the draw(_:) method to draw the pie chart:

drawPieChart()

Compile and run:

Nice going!

Draw the gradient

Cocoa Drawing uses NSGradient to paint gradients.

You need to draw a gradient in the already used sector, how to do it… ? ?

Yes, with a clipping mask!

You’ve created a path to draw the sector of the used space, let’s use it as a clipping mask before we draw the gradient.

Add this code to the drawPieChart() method:

if let gradient = NSGradient(starting: pieChartGradientStartColor,
                             ending: pieChartGradientEndColor) {
  gradient.draw(in: path, angle: Constants.pieChartGradientAngle)
}

The first line of code tries to create a gradient of two colors. If the creation is successful, the draw(in: Angle 🙂 method is called to draw it. Inside curly braces, this method sets the mask and draws the gradient inside the mask area. Isn’t it great ~

Compile and run:

Exercise/challenge: drawing a pie chart legend

Now that our custom view is getting better and better, there’s one more thing to do: draw the pie chart legend, which is the text inside.

You already know what to do. Are you ready for the challenge? ?

A few tips:

  1. usebytesFormatterTo get the free space on the hard disk (fileDistribution.availableAttribute) and the total space (fileDistribution.capacityAttribute);
  2. Calculate the position of the text to make sure your text is displayed in the center of each sector;
  3. Draws the text at the calculated location with the following properties:

    • The Font:NSFont.pieChartLegendFont;
    • Used space text color:NSColor.pieChartUsedSpaceTextColor;
    • Available Space Text Color:NSColor.pieChartAvailableSpaceTextColor.

Answer: draw legend

Add this code to the drawPieChart() method:

// 1 let availableMidAngle = (availableMidAngle) / 2 let availableMidAngle = (availableMidAngle) / 2 let halfRadius = radius / 2 //  2 let usedSpaceText = bytesFormatter.string(fromByteCount: fileDistribution.capacity) let usedSpaceTextAttributes = [ NSFontAttributeName: NSFont.pieChartLegendFont, NSForegroundColorAttributeName: NSColor.pieChartUsedSpaceTextColor] let usedSpaceTextSize = usedSpaceText.size(withAttributes: usedSpaceTextAttributes) let xPos = rect.midX + CGFloat(cos(usedMidAngle.radians)) * halfRadius - Let yPos = rect.midy + cgFloat (sin(usedMidang.radians)) * halfRadius - Draw (at: CGPoint(x: xPos, y: yPos), withAttributes: usedSpaceTextAttributes) // 3 let availableSpaceText = bytesFormatter.string(fromByteCount: fileDistribution.available) let availableSpaceTextAttributes = [ NSFontAttributeName: NSFont.pieChartLegendFont, NSForegroundColorAttributeName: NSColor.pieChartAvailableSpaceTextColor] let availableSpaceTextSize = availableSpaceText.size(withAttributes: availableSpaceTextAttributes) let availableXPos = rect.midX + cos(-availableMidAngle.radians) * halfRadius - (availableSpaceTextSize. Width / 2.0) let availableYPos. = the rect midY + sin (- availableMidAngle. Radians) * halfRadius - (availableSpaceTextSize. Height / 2.0) availableSpaceText. The draw (at: a CGPoint (x: availableXPos, y: availableYPos), withAttributes: availableSpaceTextAttributes)

Code meaning:

  1. Calculate the angles of the two regions;
  2. Creates and evaluates the properties of the used space’s textxy, and then draw it;
  3. Creates the total space of the text properties and computes itxy, and then draw it;

Now compile and run your app and enjoy your great work:

Congratulations to you! You’ve created a beautiful app using Core Graphics and Cocoa Drawing!

What’s next?

You can download the full project file here.

This MacOS Core Graphics tutorial covers the basics of the different frameworks used to draw custom views on MacOS:

  • How to create and draw paths using Core Graphics and Cocoa Drawing;
  • How to shear an area;
  • How to draw a text string;
  • How to draw gradients.

In the future, when you need to create some clean, elegant user interfaces, you should feel confident enough to whip out Core Graphics and Cocoa Drawing.

If you want to go further, check out these resources:

  • Apple 的 Introduction to Cocoa Drawing Guide
  • Apple 的 Quartz 2D Programming Guide