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

Until iOS 6, effects were common across iOS, from buttons and bars to almost any element in the UIKit. With iOS 7, Apple changed its design approach to a flatter interface. That doesn’t mean that it’s wrong or outdated to use effects! It’s still important to know how to create them.

makes it easy.

Getting Started

For this tutorial, you’ll work on a project called Cool Table. The project itself covers many topics about Core Graphics, but for this tutorial, you’ll focus on how to create a shadow and a simple gloss effect on views.

To start, click Download Materials at the top or bottom of this tutorial. Open the starter project in Xcode and run it.

bag of balls

You’ll see a grouped table consisting of two sections, each with a title and three rows. All the work you’re going to do here will be in the title view of the sections, so there’s no need to worry about the rows.

The Drawing Canvas

Right now, the table presents the section title through the tableView(_:titleForHeaderInSection:), which doesn’t allow for much customization in the header. To be able to customize it, you want to set up the header with tableView(_:viewForHeaderInSection:).

It’s worth knowing that there are a couple of ways to create a view for the header:

  • You could create the custom view using code only.
  • You could create the custom view using Interface Builder.

Both are good options, but here, you’ll take the second approach. You’ll create and customize the view in Interface Builder and supply it as the header view.

Creating the Files

In the Project navigator, create a new file using the Cocoa Touch Class template. Name the class CustomHeader. Make sure it’s a subclass of UIView and the language is Swift.

Once you have created the empty UIView subclass, create a .xib file named CustomHeader. This time, instead of choosing Cocoa Touch Class in the template selection, choose View in the User Interface group.

In the new XIB file, you’ll find one view. In the Identity inspector, change its class from UIView to CustomHeader, which is the class you just created.

Now you need the header to show the section name. Follow these three steps:

  1. Open the Library from the View menu and drag a label onto the view inside the XIB file.
  2. Create all four constraints for the label with 4 point spacing from the left and right, a distance of 0 from the top and 10 from the bottom.
  3. In the Attributes inspector, set its text alignment to center.
  4. Select the CustomHeader view again. In the Attributes inspector, change its Size in the Simulated Metrics group from Inferred to Freeform.
  5. In the Size inspector, set the height of the view to 50.

Next, in CustomHeader.swift, add this line for the UILabel outlet. Make sure to connect it in the XIB file.


@IBOutlet public var titleLabel: UILabel!

Loading the View

The next step is to load the view from the interface file and give it to the table view. To accomplish this, add the following method right after the outlet:


class func loadViewFromNib() -> CustomHeader? {
  let bundle = Bundle.main
  let nib = UINib(nibName: "CustomHeader", bundle: bundle)
  guard 
    let view = nib.instantiate(withOwner: CustomHeader())
      .first as? CustomHeader
    else {
      return nil
    }
  return view
}

loadViewFromNib() is a class method that creates and returns CustomHeader for you. Using a standard initializer won’t do the trick here.

Next, in CoolTableViewController.swift, add this method at the end of the class:


override func tableView(
  _ tableView: UITableView,
  viewForHeaderInSection section: Int
  ) -> UIView? {
  guard let customHeaderView = CustomHeader.loadViewFromNib()
    else { return nil }
  customHeaderView.titleLabel.text = self.tableView(
    tableView,
    titleForHeaderInSection: section)
  
  return customHeaderView
}

The code loads the view as explained in the previous step, sets the text of the label, and returns the new view.

Build and run. You’ll see the new header you just created with a white background.

Drawing the Masterpiece

Now that you have a canvas for your header, you’re ready for the fun part. First, consider what you need in the masterpiece you’ll draw.

The header is split into two areas. In the image above, there are three points to pay attention to.

  • A gradient with a gloss on it.
  • A small shadow right under the colored area.
  • A stroke line around the header.

The area of the shadow is 10 points. This is why the bottom constraint under the label is 10. You know the height of the full header is 50. Therefore, the colored area is 40.

Preparing the Header

Why not define those regions visually by giving each a different color? You’ll give the gradient area a red background and make the shadow area green.

In CustomHeader.swift add this line right after the title label declaration:


@IBInspectable var coloredBoxHeight: CGFloat = 40

An @IBInspectable value lets you do as much UI customization as possible directly from Interface Builder.

It’s a good practice to have any numbers declared as constants or properties instead of having many numbers scattered around in your code. Most of the time, you’ll forget the specific number you used when you look at your own code after a couple of days.

Add this method at the end of CustomHeader:


override func draw(_ rect: CGRect) {
    // 1:
    var coloredBoxRect = bounds
    coloredBoxRect.size.height = coloredBoxHeight
    
    var paperRect = bounds
    paperRect.origin.y += coloredBoxHeight
    paperRect.size.height = bounds.height - coloredBoxHeight
    
    // 2:
    let context = UIGraphicsGetCurrentContext()!
    
    context.setFillColor(UIColor.red.cgColor)
    context.fill(coloredBoxRect)
    
    context.setFillColor(UIColor.green.cgColor)
    context.fill(paperRect)
  }

draw(_:) in UIView is where you place any custom drawing code that you want to use to change the look of your custom view. The default draw method does nothing. So, make sure you are overriding the original. There are two steps happening here:

  1. Calculate the two rectangles that you’ll color. The first is the area that will have the gradient. The second is the shadowed area. Both are calculated based on coloredBoxHeight that you defined earlier.
  2. Get the current Core Graphics context and draw the two colored rectangles.

Now, change the text color of the title label in CustomHeader.xib to white directly from the Attributes inspector.

Build and run. You should see your colored headers.

Drawing Drop

Now that the rectangles are clearly defined, add the shadow. In CustomHeader.swift, add these two variables after coloredBoxHeight:


var lightColor = UIColor(red: 105/255.0, green: 179/255.0, blue: 216/255.0, alpha: 1)
var darkColor = UIColor(red: 21/255.0, green: 92/255.0, blue: 136/255.0, alpha: 1)

Next, in draw(_:) method remove the following four lines from the bottom of the method.


context.setFillColor(UIColor.red.cgColor)
context.fill(coloredBoxRect)
    
context.setFillColor(UIColor.green.cgColor)
context.fill(paperRect)

Then add these lines instead:


// 1:
let shadowColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 0.5)
// 2:
context.saveGState()  
// 3:
context.setShadow(
  offset: CGSize(width: 0, height: 2), 
  blur: 3.0,
  color: shadowColor.cgColor) 
// 4:
context.setFillColor(lightColor.cgColor)
context.fill(coloredBoxRect) 
// 5:
context.restoreGState()

That’s how you draw a shadow! Here’s what the above code means, step by step:

  1. Define the shadow as a gray color with 50% transparency.
  2. Save the current graphics state so you can apply any configuration changes you need and return back to this state when you finish.
  3. Set the shadow configuration for anything you will draw.
  4. Draw the colored box. Without this there will be no shadow applied on the screen.
  5. Return to the graphics configuration you saved above.

Finally, in CoolTableViewController.swift, at the end of tableView(_:viewForHeaderInSection:) just before the return, add the following lines:


if section == 1 {
  customHeaderView.lightColor = UIColor(
    red: 147/255.0,
    green: 105/255.0,
    blue: 216/255.0,
    alpha: 1)
  customHeaderView.darkColor = UIColor(
    red: 72/255.0,
    green: 22/255.0,
    blue: 137/255.0,
    alpha: 1)
}

This is to show a different color for the second table section. darkColor is not used yet, so don’t worry about it.

Build and run. You should see a huge improvement from last time you ran the app. The headers look better now, right? Next, you’ll add a gloss effect.

Adding a Gloss Effect

There’s more than one way to apply a gloss effect. Matt Gallagher and Michael Heyeck explained harder ways to do it, but here, you’ll learn a simple way.

For simplicity’s sake, implementing an approximation of a gloss effect by applying a gradient alpha mask is good enough approach for now.

Pro tip: this is a commonly used approach, so why not put it in a separate file for easy access in later projects? Extensions.swift is the file for that job. It has a few handy extensions that make things much easier.

At the end of the CGContext extension, add this method:


func drawGlossAndGradient(rect: CGRect, startColor: UIColor, endColor: UIColor) {
  // 1:
  drawLinearGradient(rect: rect, startColor: startColor, endColor: endColor)
  // 2:
  let glossColor1 = UIColor.white.withAlphaComponent(0.35)
  let glossColor2 = UIColor.white.withAlphaComponent(0.1)
  // 3:
  var topHalf = rect
  topHalf.size.height /= 2
  // 4:
  drawLinearGradient(rect: topHalf, startColor: glossColor1, endColor: glossColor2)
}

Here’s what the code above does:

  1. Call another method that’s in the extensions file from the sample project which draws a two-color gradient in a rectangle.
  2. Define the two white gloss colors.
  3. Calculate the rectangle that will have the white gradient, or the gloss. This rectangle is half the colored gradient area.
  4. Draw the white gradient in the smaller rectangle.

In CustomHeader.swift, at the end of draw(_:), add this line:


context.drawGlossAndGradient(
  rect: coloredBoxRect,
  startColor: lightColor,
  endColor: darkColor)

Build and run. You’ll see a nice header with a gradient color and a gloss effect.

The last thing you need is the final stroke around the colored area of the header. Right after the line you just added, add the following lines:


context.setStrokeColor(darkColor.cgColor)
context.setLineWidth(1)
context.stroke(coloredBoxRect.rectFor1PxStroke())

Build and run. Notice the darker stroke that you wanted.

Where to Go From Here?

CGContext has a lot of interesting drawing tools you can use to draw paths, lines, shapes, text, and images. It’s worth looking at the developer documentation and experimenting with it.

You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial.

I hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below.



Source link https://www.raywenderlich.com/9506-core-graphics-tutorial-shadows-and-gloss

LEAVE A REPLY

Please enter your comment!
Please enter your name here