50

Core Graphics Tutorial: Curves and Layers [FREE]

 5 years ago
source link: https://www.tuicool.com/articles/hit/BzMb2mm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Update note : Sanket Firodiya updated this tutorial for Xcode 10, iOS 12 and Swift 4.2. Ray Wenderlich wrote the original.

In this tutorial, you’ll learn how to draw on the screen using Core Graphics. You’ll learn how to draw Quadratic and Bezier curves as well as apply transforms to existing shapes. Finally, you’ll use Core Graphics layers to clone your drawings with the ease and style of a James Bond super-villain. :]

There’s a lot to cover, so make yourself comfortable, crack those knuckles and fire up Xcode. It’s time to do some drawing.

Getting Started

Start by downloading the starter project using the Download Materials button at the top or bottom of this tutorial. Once downloaded, open RageTweet.xcworkspace (not the .xcodeproj !) in Xcode.

Build and run the app. You should see the following:

1-480x270.png

In this tutorial, you’ll replace the flat blue background color with a scenic mountain background. With each change of emotions, the sky will turn a different color to represent that state.

There’s one catch — there is no source Photoshop file. This isn’t a case of exporting different background PNG files for each emotion. You’ll draw it all from scratch! :]

This is how the final output will look:

11-480x270.png

Go back to the app and swipe through the different faces and prepare to be amazed. Touch a face to send a Tweet.

Note : This project makes use of Twitter Kit to send tweets. Since iOS 11, support for Twitter through the built-in social framework has been deprecated. To learn more about how to migrate from the Social Framework and adopt Twitter Kit in your apps, check out their excellent guide on this topic. You’ll need to test on a device to be able to send Tweets, since Twitter Kit does not support sending Tweets from the simulator.

Core Graphics’ Painter’s Model

Before writing even one drawing command, it’s important you understand how things are drawn to the screen. Core Graphics utilizes a drawing model called a painter’s model . In a painter’s model, each drawing command is on top of the previous one.

This is similar to painting on an actual canvas.

When painting, you might first paint a blue sky on the canvas. When the paint has finished drying, you might paint some clouds in the sky. As you paint the clouds, the original blue color behind the clouds is obscured by fresh white paint. Next, you might paint some shadows on the clouds, and now some of the white paint is obscured by a darker paint, giving the cloud some definition.

Here’s an image straight out of Apple’s developer documentation that illustrates this idea:

core-graphics-painting-model.png

In summary, the drawing model determines the drawing order you must use.

An Image Is Worth a Thousand Words

It’s time to start drawing. Open SkyView.swift and add the following method within the class:

override func draw(_ rect: CGRect) {
  guard let context = UIGraphicsGetCurrentContext() else {
    return
  }

  let colorSpace = CGColorSpaceCreateDeviceRGB()

  //  drawSky(in: rect, context: context, colorSpace: colorSpace)
  //  drawMountains(in: rect, in: context, with: colorSpace)
  //  drawGrass(in: rect, in: context, with: colorSpace)
  //  drawFlowers(in: rect, in: context, with: colorSpace)
}

In this method, you get the context of the current graphics and create a standard color space for the device. The comments represent future drawing calls to draw the sky, mountains, grass and flowers.

Drawing the Sky

You’ll use a three color gradient to draw the sky. After draw(_:) , add the following code to do this:

private func drawSky(in rect: CGRect, context: CGContext, colorSpace: CGColorSpace?) {
  // 1
  context.saveGState()
  defer { context.restoreGState() }

  // 2
  let baseColor = UIColor(red: 148.0 / 255.0, green: 158.0 / 255.0, 
                          blue: 183.0 / 255.0, alpha: 1.0)
  let middleStop = UIColor(red: 127.0 / 255.0, green: 138.0 / 255.0, 
                           blue: 166.0 / 255.0, alpha: 1.0)
  let farStop = UIColor(red: 96.0 / 255.0, green: 111.0 / 255.0, 
                        blue: 144.0 / 255.0, alpha: 1.0)

  let gradientColors = [baseColor.cgColor, middleStop.cgColor, farStop.cgColor]
  let locations: [CGFloat] = [0.0, 0.1, 0.25]

  guard let gradient = CGGradient(
    colorsSpace: colorSpace, 
    colors: gradientColors as CFArray, 
    locations: locations) 
    else {
      return
  }

  // 3
  let startPoint = CGPoint(x: rect.size.height / 2, y: 0)
  let endPoint = CGPoint(x: rect.size.height / 2, y: rect.size.width)
  context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: [])
}

Here’s what this code does:

defer

If this code is unfamiliar to you, check out this Core Graphics tutorial , which covers gradients.

Next, in draw(_:) , uncomment the call to drawSky(in:context:colorSpace:) .

Build and run the app. Now you should see the following:

2-480x270.png

You’ll notice that the actual gradient occurs near the top of the rectangle as opposed to being evenly applied throughout the whole of it.

This is the actual sky portion of the drawing. The lower half will be obscured by subsequent drawing calls.

With the sky complete, it’s now time to draw the mountains.

Getting Comfortable with Curves

Take a look at the source drawing and observe the mountains. While you can certainly produce the same effect by drawing a series of arcs, a much easier method is to use curves.

There are two kinds of curves available in Core Graphics. One is called a Quadratic Curve and its big brother is known as a Bezier Curve. These curves are based on mathematical principles that allow them to infinitely scale, no matter the resolution.

curve-examples-480x219.png

If looking at that diagram makes your stomach twist into knots, take a deep breath followed by a shot of whiskey. :]

Now look at it again. Feel better? No? All right. Have another shot and realize this… you do not need to know any mathematical concepts to draw these beasts.

In practice, these curves are actually quite easy to draw. Like any line, you first need to know a starting point and an ending point. Then, you add a control point.

A control point essentially dictates the curve of the line. The closer you place the control point to the line, the less dramatic the curve. By placing the control point farther away from the line, the more pronounced the curve. Think of a control point as a little magnet that pulls the line towards it.

On a practical level, the main difference between a Quadratic Curve and a Bezier Curve is the number of control points. A Quadratic curve has one control point. A Bezier curve has two. That’s about it.

Note : Another good way to develop an intuitive understanding of how control points affect Bezier curves is to spend some time playing around with them at http://cubic-bezier.com

In SkyView.swift , just underneath drawSky(in:context:colorSpace:) , add this new method:

private func drawMountains(in rect: CGRect, in context: CGContext, 
                   with colorSpace: CGColorSpace?) {
  let darkColor = UIColor(red: 1.0 / 255.0, green: 93.0 / 255.0, 
                          blue: 67.0 / 255.0, alpha: 1)
  let lightColor = UIColor(red: 63.0 / 255.0, green: 109.0 / 255.0, 
                           blue: 79.0 / 255.0, alpha: 1)
  let rectWidth = rect.size.width

  let mountainColors = [darkColor.cgColor, lightColor.cgColor]
  let mountainLocations: [CGFloat] = [0.1, 0.2]
  guard let mountainGrad = CGGradient.init(colorsSpace: colorSpace, 
        colors: mountainColors as CFArray, locations: mountainLocations) else {
    return
  }

  let mountainStart = CGPoint(x: rect.size.height / 2, y: 100)
  let mountainEnd = CGPoint(x: rect.size.height / 2, y: rect.size.width)

  context.saveGState()
  defer { context.restoreGState() }

  // More coming 1...
}

This code sets up the basis of the method, where more code will follow shortly. It creates some colors and points that you’ll use later.

As you can see from the source diagram, the mountains start out a deep green and subtly change to a light tanish color. Now, it’s time to draw the actual curves. You’ll start with a Quadratic curve.

Within the same method, replace // More coming 1... with the following:

let backgroundMountains = CGMutablePath()
backgroundMountains.move(to: CGPoint(x: -5, y: 157), transform: .identity)
backgroundMountains.addQuadCurve(to: CGPoint(x: 77, y: 157), 
                                 control: CGPoint(x: 30, y: 129), 
                                 transform: .identity)

// More coming 2...

The first thing you’re doing here is creating a path object. You add a single quadratic curve to the path. The move(to:transform:) call sets the starting point of the line. The next bit is where all the magic happens.

backgroundMountains.addQuadCurve(to: CGPoint(x: 77, y: 157), 
  control: CGPoint(x: 30, y: 129), transform: .identity)
CGPoint
CGPoint

In short, you now have a quadratic curve.

To see this in action, replace the // More coming 2... with the following:

// Background Mountain Stroking
context.addPath(backgroundMountains)
context.setStrokeColor(UIColor.black.cgColor)
context.strokePath()

// More coming 3...

This draws the path, in black, on the graphics context.

Next, in draw(_:) , uncomment the call to drawMountains(in:in:with:) .

Now, build and run the app. You should see the following:

3-480x270.png

You’ve created a nice little curve here. It’s not the Mona Lisa, but it’s a start.

Now it’s time to tackle a Bezier curve. Back in drawMountains(in:in:with:) , underneath backgroundMountains.addQuadCurve... , add the following:

backgroundMountains.addCurve(to: CGPoint(x: 303, y: 125), 
                             control1: CGPoint(x: 190, y: 210), 
                             control2: CGPoint(x: 200, y: 70), 
                             transform: .identity)

The big difference between this call and the previous call is the addition of another set of x and y points for the next control point. This one is a bezier curve rather than a quadratic curve.

  • The first argument is a CGPoint with the values 303 and 125 for x and y, respectively. This indicates the end of the line.
  • The second argument is a CGPoint with the values 190 and 210 for x and y, respectively. This indicates the position of the first control point.
  • The third argument is a CGPoint with the values 200 and 70 for x and y, respectively. This indicates the position of the second control point.
  • Like before, backgroundMountains is a CGPath , and you’re applying the default identity transform to it.

Build and run the app and you should now see the following:

4-480x270.png

The key thing to remember about curves is that the more you use them, the easier it is to determine the placement of the control points for your desired curves.

Now it’s time to complete the first set of mountains. Add the following code right under the line you just added:

backgroundMountains.addQuadCurve(to: CGPoint(x: 350, y: 150), 
                                 control: CGPoint(x: 340, y: 150), 
                                 transform: .identity)
backgroundMountains.addQuadCurve(to: CGPoint(x: 410, y: 145), 
                                 control: CGPoint(x: 380, y: 155), 
                                 transform: .identity)
backgroundMountains.addCurve(to: CGPoint(x: rectWidth, y: 165), 
                             control1: CGPoint(x: rectWidth - 90, y: 100), 
                             control2: CGPoint(x: rectWidth - 50, y: 190), 
                             transform: .identity)
backgroundMountains.addLine(to: CGPoint(x: rectWidth - 10, y: rect.size.width),
                            transform: .identity)
backgroundMountains.addLine(to: CGPoint(x: -5, y: rect.size.width), 
                            transform: .identity)
backgroundMountains.closeSubpath()

// Background Mountain Drawing
context.addPath(backgroundMountains)
context.clip()
context.drawLinearGradient(mountainGrad, start: mountainStart, 
                           end: mountainEnd, options: [])
context.setLineWidth(4)

This finishes the mountains with a few more curves that extend beyond the length of the device. It also adds in drawing of the gradient for the mountains.

Build and run the app. It should look like the following:

5-480x270.png

Now, add some foreground mountains. Replace // More coming 3... with the following code:

// Foreground Mountains
let foregroundMountains = CGMutablePath()
foregroundMountains.move(to: CGPoint(x: -5, y: 190), 
                         transform: .identity)
foregroundMountains.addCurve(to: CGPoint(x: 303, y: 190), 
                             control1: CGPoint(x: 160, y: 250), 
                             control2: CGPoint(x: 200, y: 140), 
                             transform: .identity)
foregroundMountains.addCurve(to: CGPoint(x: rectWidth, y: 210), 
                             control1: CGPoint(x: rectWidth - 150, y: 250), 
                             control2: CGPoint(x: rectWidth - 50, y: 170), 
                             transform: .identity)
foregroundMountains.addLine(to: CGPoint(x: rectWidth, y: 230), 
                            transform: .identity)
foregroundMountains.addCurve(to: CGPoint(x: -5, y: 225), 
                             control1: CGPoint(x: 300, y: 260), 
                             control2: CGPoint(x: 140, y: 215), 
                             transform: .identity)
foregroundMountains.closeSubpath()

// Foreground Mountain drawing
context.addPath(foregroundMountains)
context.clip()
context.setFillColor(darkColor.cgColor)
context.fill(CGRect(x: 0, y: 170, width: rectWidth, height: 90))

// Foreground Mountain stroking
context.addPath(foregroundMountains)
context.setStrokeColor(UIColor.black.cgColor)
context.strokePath()

This adds some more mountains in front of the ones you just added.

Now build and run the app. You should see the following:

6-480x270.png

With just a few curves and a splash of gradients, you can already construct a nice looking background!

Drawing the Grass

Adding grass uses a combination of all the things you’ve just learned.

Add the following method in SkyView.swift underneath drawMountains(in:in:with:) :

private func drawGrass(in rect: CGRect, in context: CGContext, 
                       with colorSpace: CGColorSpace?) {
  // 1
  context.saveGState()
  defer { context.restoreGState() }

  // 2
  let grassStart = CGPoint(x: rect.size.height / 2, y: 100)
  let grassEnd = CGPoint(x: rect.size.height / 2, y: rect.size.width)
  let rectWidth = rect.size.width

  let grass = CGMutablePath()
  grass.move(to: CGPoint(x: rectWidth, y: 230), transform: .identity)
  grass.addCurve(to: CGPoint(x: 0, y: 225), control1: CGPoint(x: 300, y: 260), 
                 control2: CGPoint(x: 140, y: 215), 
                 transform: .identity)
  grass.addLine(to: CGPoint(x: 0, y: rect.size.width), 
                transform: .identity)
  grass.addLine(to: CGPoint(x: rectWidth, y: rect.size.width), 
                transform: .identity)

  context.addPath(grass)
  context.clip()

  // 3
  let lightGreen = UIColor(red: 39.0 / 255.0, green: 171.0 / 255.0, 
                           blue: 95.0 / 255.0, alpha: 1)

  let darkGreen = UIColor(red: 0.0 / 255.0, green: 134.0 / 255.0, 
                          blue: 61.0 / 255.0, alpha: 1)

  let grassColors = [lightGreen.cgColor, darkGreen.cgColor]
  let grassLocations: [CGFloat] = [0.3, 0.4]
  if 
    let grassGrad = CGGradient.init(colorsSpace: colorSpace, 
    colors: grassColors as CFArray, locations: grassLocations) {
      context.drawLinearGradient(grassGrad, start: grassStart, 
                                 end: grassEnd, options: [])
  }
}

Here’s what that code does:

  1. As usual, save the graphics state and make sure it’s restored at the end of the function.
  2. This sets up the path with which to clip the subsequent gradient. This is to keep the grass gradient limited to the bottom of the screen.
  3. This draws the gradient from lovely light green to dark green.

To see it in action, uncomment drawGrass(in:in:with:) in draw(_:) .

Now build and run, and it should look like the following:

7-480x270.png

Affable Affine Transforms

flowers1-250x250.png

The next step in the process is to add some flowers to the grass.

Take a close look at the source image. Instead of looking at the three flowers, just pick one and take a closer look at how it’s drawn. You’ll see that each flower is composed of various circles — one for the center and five for the petals. A small curve represents the stem.

Drawing circles is no problem. There’s a method called addEllipse(in:) . All you need to do is define a CGRect and this method will draw an ellipse in the center of it.

Of course, there’s a catch. CGRect s can only be vertical or horizontal. What if you wanted the ellipse to be drawn at a forty-degree angle?

Introducing affine transforms . Affine transforms modify a coordinate system while still maintaining points, lines, and shapes. These mathematical functions allow you to rotate, scale, move, and even combine your objects.

Since you want to rotate your object, you’ll want to use the CGAffineTransform(rotationAngle:) . Here’s how you call it:

CGAffineTransform(rotationAngle: radians)

A radian is just a measure of angles. Since most people think in terms of degrees as opposed to radians, a simple helper method can make this function call easier to use.

Add the following just before draw(_:) :

private func degreesToRadians(_ degrees: CGFloat) -> CGFloat {
  return CGFloat.pi * degrees/180.0
}

This method simply converts a value from degrees to radians.

Now, rotating a CGRect is just a matter of supplying an angle. For example, if you want to rotate something 45 degrees, you use the following transform:

let transform = CGAffineTransform(rotationAngle: degreesToRadians(45))

Pretty easy, eh? Unfortunately, there’s another catch. Rotating paths can be a little frustrating.

Typically, you’ll want to rotate a path around a particular point. Since a path is just a collection of points, there’s no center position — just the origin. Thus, when you rotate the ellipse, it appears in a different x and y position from where you started.

To make it work, you must reset the origin point, rotate the path, and then restore the previous point. Instead of doing all of this in one method, create a new method for drawing each petal. Just after drawGrass(in:in:with:) , add this new method:

private func drawPetal(in rect: CGRect, inDegrees degrees: Int, 
                       inContext context: CGContext) {
  // 1
  context.saveGState()
  defer { context.restoreGState() }

  // 2
  let midX = rect.midX
  let midY = rect.midY
  let transform = CGAffineTransform(translationX: -midX, y: -midY)
    .concatenating(CGAffineTransform(rotationAngle: degreesToRadians(CGFloat(degrees))))
    .concatenating(CGAffineTransform(translationX: midX, y: midY))

  // 3
  let flowerPetal = CGMutablePath()
  flowerPetal.addEllipse(in: rect, transform: transform)
  context.addPath(flowerPetal)
  context.setStrokeColor(UIColor.black.cgColor)
  context.strokePath()
  context.setFillColor(UIColor.white.cgColor)
  context.addPath(flowerPetal)
  context.fillPath()
}

Here’s what that code does:

CGRect

Creating a flower should be rather easy. Add this method just after drawPetal(in:inDegrees:inContext:) :

private func drawFlowers(in rect: CGRect, in context: CGContext, 
                         with colorSpace: CGColorSpace?) {
  // 1
  context.saveGState()
  defer { context.restoreGState() }

  // 2
  drawPetal(in: CGRect(x: 125, y: 230, width: 9, height: 14), 
            inDegrees: 0, inContext: context)
  drawPetal(in: CGRect(x: 115, y: 236, width: 10, height: 12), 
            inDegrees: 300, inContext: context)
  drawPetal(in: CGRect(x: 120, y: 246, width: 9, height: 14), 
            inDegrees: 5, inContext: context)
  drawPetal(in: CGRect(x: 128, y: 246, width: 9, height: 14), 
            inDegrees: 350, inContext: context)
  drawPetal(in: CGRect(x: 133, y: 236, width: 11, height: 14), 
            inDegrees: 80, inContext: context)

  // 3
  let center = CGMutablePath()
  let ellipse = CGRect(x: 126, y: 242, width: 6, height: 6)
  center.addEllipse(in: ellipse, transform: .identity)

  let orangeColor = UIColor(red: 255 / 255.0, green: 174 / 255.0, 
                            blue: 49.0 / 255.0, alpha: 1.0)

  context.addPath(center)
  context.setStrokeColor(UIColor.black.cgColor)
  context.strokePath()
  context.setFillColor(orangeColor.cgColor)
  context.addPath(center)
  context.fillPath()

  // 4
  context.move(to: CGPoint(x: 135, y: 249))
  context.setStrokeColor(UIColor.black.cgColor)
  context.addQuadCurve(to: CGPoint(x: 133, y: 270), control: CGPoint(x: 145, y: 250))
  context.strokePath()
}

This code does the following:

  1. Save the graphics state.
  2. Draw 5 petals using the method you just created.
  3. Draw an orange circle for the middle of the flower.
  4. Draw the stem using a single quadratic curve.

Now, uncomment drawFlowers(in:in:with:) in draw(_:) .

Build and run. You should now see a nice flower just underneath the mountains.

8-480x270.png

Attack of the Clones

Drawing the next two flowers should be a relatively easy affair, but Core Graphics provides a way to make it even easier. Instead of figuring out the measurements for two new flowers, you can simply clone the existing one and make a field of them.

Note : To make this even nicer, you could make several permutations of the flower and randomly select flowers when creating your field. This would give the field a diverse and organic feel.

Core Graphics allows you to make copies of your drawings through CGLayer objects. Instead of drawing to the main graphics context, you draw to the layer context. Once you finish drawing to a CGLayer , it acts as a factory, pumping out copies of each drawing. The drawings are cached, making it faster than using regular drawing calls.

A great example of using a CGLayer is the United States flag. The flag contains fifty stars against a blue background. While you could loop through the drawing instructions for one star at a time, the faster method is to draw the star to a CGLayer and then make copies of that star.

Replace drawFlowers(in:in:with:) with the following:

private func drawFlowers(in rect: CGRect, in context: CGContext, 
                         with colorSpace: CGColorSpace?) {
  context.saveGState()
  defer { context.restoreGState() }

  // 1
  let flowerSize = CGSize(width: 300, height: 300)

  // 2
  guard let flowerLayer = CGLayer(context, size: flowerSize, 
                                  auxiliaryInfo: nil) else {
    return
  }

  // 3
  guard let flowerContext = flowerLayer.context else {
    return
  }

  // Draw petals of the flower
  drawPetal(in: CGRect(x: 125, y: 230, width: 9, height: 14), inDegrees: 0, 
            inContext: flowerContext)
  drawPetal(in: CGRect(x: 115, y: 236, width: 10, height: 12), inDegrees: 300, 
            inContext: flowerContext)
  drawPetal(in: CGRect(x: 120, y: 246, width: 9, height: 14), inDegrees: 5, 
            inContext: flowerContext)
  drawPetal(in: CGRect(x: 128, y: 246, width: 9, height: 14), inDegrees: 350, 
            inContext: flowerContext)
  drawPetal(in: CGRect(x: 133, y: 236, width: 11, height: 14), inDegrees: 80, 
            inContext: flowerContext)

  let center = CGMutablePath()
  let ellipse = CGRect(x: 126, y: 242, width: 6, height: 6)
  center.addEllipse(in: ellipse, transform: .identity)

  let orangeColor = UIColor(red: 255 / 255.0, green: 174 / 255.0, 
                            blue: 49.0 / 255.0, alpha: 1.0)

  flowerContext.addPath(center)
  flowerContext.setStrokeColor(UIColor.black.cgColor)
  flowerContext.strokePath()
  flowerContext.setFillColor(orangeColor.cgColor)
  flowerContext.addPath(center)
  flowerContext.fillPath()

  flowerContext.move(to: CGPoint(x: 135, y: 249))
  context.setStrokeColor(UIColor.black.cgColor)
  flowerContext.addQuadCurve(to: CGPoint(x: 133, y: 270), 
                             control: CGPoint(x: 145, y: 250))
  flowerContext.strokePath()
}

How this works:

  1. Set the size of the object that you’re drawing.
  2. Create a new layer by passing the current graphics context.
  3. Extract the layer’s graphic context. From this point onward, you draw to the layer’s context instead of the main graphics context.

Once the flower is complete, the only remaining thing to do is print copies.

Now add the following at the end of the function:

// Draw clones
context.draw(flowerLayer, at: CGPoint(x: 0, y: 0))
context.translateBy(x: 20, y: 10)
context.draw(flowerLayer, at: CGPoint(x: 0, y: 0))
context.translateBy(x: -30, y: 5)
context.draw(flowerLayer, at: CGPoint(x: 0, y: 0))
context.translateBy(x: -20, y: -10)
context.draw(flowerLayer, at: CGPoint(x: 0, y: 0))

This draws 4 clones of the flower at various points.

Build and run, and you should see the following:

9-480x270.png

Finishing the App

Congratulations! You made it this far! It’s now time to add the finishing touches to this future best seller.

For each change in emotion, the sky should reflect that state. Each swipe on the scroll view resets the rage level in SkyView . Make the following changes to reflect such inner turmoil. :]

Replace drawSky(in:context:colorSpace:) with the following:

private func drawSky(in rect: CGRect, rageLevel: RageLevel, context: CGContext, 
                     colorSpace: CGColorSpace) {
  let baseColor: UIColor
  let middleStop: UIColor
  let farStop: UIColor

  switch rageLevel {
  case .happy:
    baseColor = UIColor(red: 0 / 255.0, green: 158.0 / 255.0, 
                        blue: 183.0 / 255.0, alpha: 1.0)
    middleStop = UIColor(red: 0.0 / 255.0, green: 255.0 / 255.0, 
                         blue: 252.0 / 255.0, alpha: 1.0)
    farStop = UIColor(red: 255.0 / 255.0, green: 255.0 / 255.0, 
                      blue: 255.0 / 255.0, alpha: 1.0)
  case .somewhatHappy:
    baseColor = UIColor(red: 0 / 255.0, green: 158.0 / 255.0, 
                        blue: 183.0 / 255.0, alpha: 1.0)
    middleStop = UIColor(red: 144.0 / 255.0, green: 152.0 / 255.0, 
                         blue: 253.0 / 255.0, alpha: 1.0)
    farStop = UIColor(red: 96.0 / 255.0, green: 111.0 / 255.0, 
                      blue: 144.0 / 255.0, alpha: 1.0)
  case .neutral:
    baseColor = UIColor(red: 148.0 / 255.0, green: 158.0 / 255.0, 
                        blue: 183.0 / 255.0, alpha: 1.0)
    middleStop = UIColor(red: 127.0 / 255.0, green: 138.0 / 255.0, 
                         blue: 166.0 / 255.0, alpha: 1.0)
    farStop = UIColor(red: 96.0 / 255.0, green: 111.0 / 255.0, 
                      blue: 144.0 / 255.0, alpha: 1.0)
  case .somewhatAngry:
    baseColor = UIColor(red: 255.0 / 255.0, green: 147.0 / 255.0, 
                        blue: 167.0 / 255.0, alpha: 1.0)
    middleStop = UIColor(red: 127.0 / 255.0, green: 138.0 / 255.0, 
                         blue: 166.0 / 255.0, alpha: 1.0)
    farStop = UIColor(red: 107.0 / 255.0, green: 107.0 / 255.0, 
                      blue: 107.0 / 255.0, alpha: 1.0)
  case .angry:
    baseColor = UIColor(red: 255.0 / 255.0, green: 0 / 255.0, 
                        blue: 0 / 255.0, alpha: 1.0)
    middleStop = UIColor(red: 140.0 / 255.0, green: 33.0 / 255.0, 
                         blue: 33.0 / 255.0, alpha: 1.0)
    farStop = UIColor(red: 0 / 255.0, green: 0 / 255.0, 
                      blue: 0 / 255.0, alpha: 1.0)
  }

  context.saveGState()
  defer { context.restoreGState() }

  let gradientColors = [baseColor.cgColor, middleStop.cgColor, farStop.cgColor]
  let locations: [CGFloat] = [0.0, 0.1, 0.25]

  let startPoint = CGPoint(x: rect.size.height/2, y: 0)
  let endPoint = CGPoint(x: rect.size.height/2, y: rect.size.width)

  if let gradient = CGGradient.init(colorsSpace: colorSpace, 
                                    colors: gradientColors as CFArray, 
                                    locations: locations) {
    context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: [])
  }
}

This simply adds in a rageLevel: parameter to the function and then changes the colors of the gradient depending on that parameter value.

Next, replace the call to drawSky(in:context:colorSpace:) in draw(_:) with the following:

drawSky(in: rect, rageLevel: rageLevel, context: context, colorSpace: colorSpace)

You’re now passing in the rageLevel to drawSky(in:rageLevel:context:colorSpace:) .

Build and run, then swipe through the different faces. Challenge dominated! Hello five star rating! :]

10-480x270.png

Where to Go From Here

By this point, you should have a firm grasp of the fundamentals of Core Graphics and should be well on your way to using it in your own projects.

If you’re interested in learning more about the UIKit Drawing System, go through Apple’s excellent UIKIT Drawing System resource .

Also check out ourArcs and Paths tutorial, which builds on the knowledge you’ve acquired in this tutorial and focuses on drawing arcs and other geometric shapes.

You may also want to check our Lines, Rectangles, and Gradients tutorial, which covers gradients and drawing lines and rectangles using Core Graphics.

I hope you found this tutorial useful. Please share any comments or questions in the forum discussion below!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK