Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Quartz with Swift 3

Quartz with Swift 3

Presentation by Bob McCune of TapHarmonic detailing how to use the Quartz framework and Swift 3 to perform 2D drawing on Apple platforms.

Bob McCune

March 15, 2017
Tweet

More Decks by Bob McCune

Other Decks in Technology

Transcript

  1. -Overview of the Quartz framework - What is it? -

    When do you need it? -Core capabilities of framework: - Contexts and Paths - Drawing lines, curves, and images - Applying transformations -Recipes and Examples Agenda What will I learn?
  2. -Apple’s 2D drawing framework based on the PDF imaging model

    -Graphics API to produce lines, curves, transforms, gradients, etc. -Uses painters model for drawing operations What is Quartz 2D? Wasn’t this supposed to be about Core Graphics? Drawing Order Result
  3. -Prefer to build your UI using images - Better performance

    and caching - Easier to create and maintain - Keeps your graphics designers employed -It’s best to design your UI using images… When do I need it? Isn’t this what graphics designers are for? ...until you can’t
  4. Objective-C CGContext *context = [[CGContext alloc] init]; [context moveToPoint:CGPointMake(12.0f, 12.0f)];

    [context addLineToPoint:CGPointMake(24.0f, 24.0f)]; [context fill]; Understanding the Syntax Dude, where’s my objects?
  5. Understanding the Syntax Dude, where’s my objects? Objective-C CGContext *context

    = [[CGContext alloc] init]; [context moveToPoint:CGPointMake(12.0f, 12.0f)]; [context addLineToPoint:CGPointMake(24.0f, 24.0f)]; [context fill]; Standard C CGContextRef context = UIGraphicsGetCurrentContext(); CGContextMoveToPoint(context, 12.0f, 12.0f); CGContextAddLineToPoint(context, 24.0f, 24.0f); CGContextFillPath(context); Quartz not an Objective-C API
  6. Understanding the Syntax Dude, where’s my objects? Standard C CGContextRef

    context = UIGraphicsGetCurrentContext(); CGContextMoveToPoint(context, 12.0f, 12.0f); CGContextAddLineToPoint(context, 24.0f, 24.0f); CGContextFillPath(context); Swift 2 let context = UIGraphicsGetCurrentContext()! CGContextMoveToPoint(context, 12.0, 12.0) CGContextAddLineToPoint(context, 24.0, 24.0) CGContextFillPath(context)
  7. Understanding the Syntax Dude, where’s my objects? Swift 3 let

    context = UIGraphicsGetCurrentContext()! context.move(to: CGPoint(x: 12.0, y: 12.0)) context.addLine(to: CGPoint(x: 24.0, y: 24.0)) context.fillPath() Swift 2 let context = UIGraphicsGetCurrentContext()! CGContextMoveToPoint(context, 12.0, 12.0) CGContextAddLineToPoint(context, 24.0, 24.0) CGContextFillPath(context) Session 403: Swift API Design Guidelines (WWDC 2016)
  8. -Graphics context provides the drawing area -Manages core drawing states:

    - Colors, line widths, transforms, etc. - It’s a state machine -Key Quartz Contexts: - CGContext - CGBitmapContext - CGPDFContext Quartz Graphics Contexts The Canvas
  9. -Current Transformation Matrix (CTM) defines the coordinate system -Coordinate system

    varies on macOS and iOS/tvOS Quartz Coordinate System Drawing Orientation 0, 0 y x Positive 0, 0 y x Positive macOS iOS and tvOS
  10. -Drawing is defined in terms of points -Points are abstract,

    infinitely thin -Points live in User Space CGContext Drawing Quartz Points p0 p1
  11. -CGContext is the default drawing context - Created by UIKit

    and AppKit -Get a reference to it: CGContext Drawing CGContext let context = UIGraphicsGetCurrentContext() iOS and tvOS let context = NSGraphicsContext.current()?.graphicsPort macOS
  12. Getting Started Where do I draw? class HelloQuartzViewController: UIViewController {

    func drawBackground(recognizer: UIGestureRecognizer) { } } let context = UIGraphicsGetCurrentContext() context?.setFillColor(randomColor()) context?.fill(view.bounds)
  13. Getting Started Where do I draw? class HelloQuartzView: UIView {

    override func draw(_ rect: CGRect) { } } class HelloQuartzViewController: UIViewController { func drawBackground(recognizer: UIGestureRecognizer) { } } let context = UIGraphicsGetCurrentContext() context?.setFillColor(randomColor()) context?.fill(view.bounds)
  14. Getting Started Where do I draw? class HelloQuartzView: UIView {

    override func draw(_ rect: CGRect) { } } class HelloQuartzViewController: UIViewController { func drawBackground(recognizer: UIGestureRecognizer) { } } let context = UIGraphicsGetCurrentContext() context?.setFillColor(randomColor()) context?.fill(view.bounds) // Trigger call to HelloQuartzView's draw(:) method helloView.setNeedsDisplay()
  15. CGContext Drawing Defining Paths (0, 0) (100, 100) let rect

    = CGRect(x: 0, y: 0, width: 100, height: 100) context.addRect(rect)
  16. CGContext Drawing Defining Paths (0, 0) (100, 100) let rect

    = CGRect(x: 0, y: 0, width: 100, height: 100) context.addEllipse(in: rect)
  17. -Colors must be defined on the context before drawing -Fill

    Colors - setFillColor(color: CGColor) - setFillColor(gray: CGFloat, alpha: CGFloat) - setFillColor(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) -Stroke Colors - setStrokeColor(color: CGColor) - setStrokeColor(gray: CGFloat, alpha: CGFloat) - setStrokeColor(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) Defining Colors CGColor
  18. - CGColor is the native Quartz color type - UIColor

    is the native UIKit color type -Use UIColor in almost all cases - Easier to create - Named color initializers - Easy to convert to and from CGColor Defining Colors CGColor vs UIColor // Convert to Quartz Type let cgColor = UIColor.red.cgColor context.setFillColor(cgColor) // Convert to UIKit Type let uiColor = UIColor(cgColor: cgColor)
  19. -Lines are the most essential primitive -Lines are always defined

    by their endpoints -Basic Recipe: 1. Begin a new path 2. Move to initial starting point 3. Add lines defining shape 4. Close the path (optional) 5. Draw Path Drawing Lines Defining Paths
  20. -Arcs define circle segments -Arcs are defined with the following:

    - Center x, y coordinates - Radius - Start and stop angles (in radians) - Direction (clockwise or counter-clockwise) -Created using: - addArc(center:radius:startAngle:endAngle:clockwise:) - addArc(tangent1End:tangent2End:radius:) Drawing Arcs Constructing Paths
  21. -Arc functions are not aware of flipped coordinate system -

    Need to think upside down (or) - Transform coordinate system before using - UIBezierPath wraps Quartz GGPath functionality - Access to underlying path with CGPath property -Generally easier to use UIBezierPath on iOS and tvOS - * In Swift 3, direct Quartz usages is very similar UIBezierPath More iOS/tvOS-friendly solution
  22. Drawing Arcs Defining Paths let path = UIBezierPath(arcCenter: centerPoint, radius:

    radius, startAngle: 0, endAngle: degreesToRadians(270.0), clockwise: false) path.addLine(to: centerPoint) context.addPath(path.cgPath)
  23. Cubic Bézier Curves Constructing Paths p0 cp1 cp2 p1 -cp1

    -cp2 let tx = CGAffineTransform(scaleX: -1, y: 1) context.addCurve(to: p0, control1: cp2.applying(tx), control2: cp1.applying(tx))
  24. Cubic Bézier Curves Constructing Paths p0 cp1 cp2 p1 cp1

    -cp2 context.closePath() context.setFillColor(UIColor.red.cgColor) context.fillPath()
  25. -Quartz uses fill rules to determine what’s inside and outside

    of a path. -Uses one of the following: - Non-zero Winding Rule (Default) - Even Odd Rule Fill Rules To fill, or not to fill
  26. -Affine transformations are essential to all graphics programming regardless of

    platform -Allow you to manipulate the coordinate system to translate, scale, skew, and rotate -Transform preserves collinearity of lines Affine Transformations Transforming the Coordinate System X
  27. -CTM can be modified with the following: - translateBy(x:y:) -

    scaleBy(x:y:) - rotate(by:) Current Transformation Matrix Modifying the CTM
  28. -A gradient is a fill that varies from one color

    to another -Quartz supports two different gradient types - Linear varies between two endpoints - Radial varies between two radial endpoints Gradients Drawing Gradients Linear Radial
  29. -Gradients can be created using either: - init(colorsSpace:colors:locations:) - init(colorSpace:colorComponents:locations:)

    -Both functions require the following: - CGColorSpace - Colors (components or array of CGColor) - Array of CGFloat values defining gradient color stops Creating a CGGradient CGGradient
  30. CGGradientRef Building the gradient code let startColor = UIColor.red let

    endColor = UIColor.orange let colors = [startColor.cgColor, endColor.cgColor] as CFArray let locations: [CGFloat] = [0, 1] let colorSpace = CGColorSpaceCreateDeviceRGB() let gradient = CGGradient(colorsSpace: colorSpace, colors: colors, locations: locations)! let startPoint = CGPoint(x: rect.midX, y: rect.minY) let endPoint = CGPoint(x: rect.midX, y: rect.maxY) let opts = CGGradientDrawingOptions() context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: opts)
  31. -Quartz has broad support for images -Represented as CGImage -Can

    be drawn using: - draw(image:rect:) - draw(image:rect:byTiling) - CGBitmapContext allows for dynamically building image data Working with Images CGImage
  32. -Don’t draw in Quartz. Use UIImageView. -Choose UIImage over CGImage

    - Easier to load and cache image content - Provides methods for drawing - Is aware flipped coordinate system - UIImage provides a CGImage property -Use CGImage for more advanced cases - Cropping, scaling, masking - Dynamic image creation CGImage vs UIImage Which should I use?
  33. Using Image Masks Hiding behind a mask // Mask image

    created by toMask() extension guard let maskImage = UIImage(named: “round_mask")?.cgImage?.toMask() else { return } guard let jeffersonImage = UIImage(named: "jefferson")?.cgImage else { return } if let masked = jeffersonImage.masking(maskImage) { // Draw shadow to offset from background let shadowOffset = CGSize(width: 1, height: 1) let shadowColor = UIColor.darkGray.cgColor context.setShadow(offset: shadowOffset, blur: 12, color: shadowColor) let maskedImage = UIImage(cgImage: masked) maskedImage.draw(in: drawingRect(for: maskedImage)) }
  34. Cropping Images cropping(to:) let albumImage = UIImage(named: "vh1")! let cropRect

    = CGRect(x: 20, y: 20, width: 200, height: 200) if let cgImage = albumImage.cgImage?.cropping(to: cropRect) { // Create new UIImage and use as needed let eddieImage = UIImage(cgImage: cgImage) }
  35. -Quartz is a powerful drawing engine for Apple platforms -Essential

    skill to master to build beautiful apps -Quartz + Swift 3 = Awesome Summary