MCSv3

Mobile Development


Ilya Loshkarev
loshkarev.i@gmail.com

Graphics

Graphics Context

Context contains drawing parameters
and all information needed to render the paint

Graphical Contexts are organized in form of a stack

Current Context


						override func draw(_ rect: CGRect) {
							// Get view's current graphics context
							if let context = UIGraphicsGetCurrentContext() {
								/* draw something */
							}
						}
						

Image Context


							// Create bitmap and make it current context
							UIGraphicsBeginImageContext(image.size)
							// Copy image into current context
							image.draw(in: CGRect(origin: CGPoint.zero,
									size: image.size))
							/* draw something  on top of the image*/
							// Return an image from current bitmap-based context
							image = UIGraphicsGetImageFromCurrentImageContext()
							// Remove current bitmap context
							UIGraphicsEndImageContext()
						

Drawing with UI Graphics

All drawing happens in current graphics context


							func drawLine(from fromPoint: CGPoint, to toPoint: CGPoint) {
								// Create Path
								let linePath = UIBezierPath()
								linePath.moveToPoint(fromPoint)
								linePath.addLineToPoint(toPoint)
								// Set up context parameters
								UIColor.blue.setStroke()
								linePath.lineWidth = 10
								linePath.lineCapStyle = .round
								// Draw path in context
								linePath.stroke()
							}
						

Draw order is important

Paths are drawn on top of each other

Drawing with Core Graphics


							context.addRect(rect: CGRect(x: 0, y:0,
							                             width: 250, height: 250))
							context.setFillColor(UIColor.white.cgColor)
							context.setStrokeColor(UIColor.black.cgColor)
							context.setLineWidth(5)
							context.fillPath()
							context.strokePath()
						

Core Graphics Gradient


						let colors = [startColor.cgColor, endColor.cgColor]
						let colorLocations:[CGFloat] = [0.0, 1.0]
						let colorSpace = CGColorSpaceCreateDeviceRGB()
						let gradient = CGGradient(colorsSpace: colorSpace,
						                          colors: colors as CFArray,
						                          locations: colorLocations)
						context.drawLinearGradient(gradient,
						                       start: CGPoint.zero,
						                       end: CGPoint(x: 1, y: 1),
						                       options: .drawsAfterEndLocation)
						

Layers

Core Graphics Layer

An offscreen context for reusing content
drawn with Core Graphics

    • Repeated drawing
    • Offscreen rendering

Creating a Layer


							let layer = CGLayer(context, size: CGSize(width: 100, height: 100), auxiliaryInfo: nil)
							let layerContext = layer?.context
							/* draw something */
							context.draw(layer, at: CGPoint.zero)
						

Core Animation Layer

An object that manages image-based content
and allows you to perform animations on that content

Completely unrelated to CGLayer

View's Layer

Every view has underlying CALayer


							view.layer.contents = UIImage(named: "swift")?.cgImage
							// Layers can have sublayers
							let sublayer = CALayer()
							sublayer.frame = CGRect(x: 50, y: 50, width: 100, height: 100)
							sublayer.backgroundColor = UIColor.red.cgColor
							view.layer.addSublayer(sublayer)
						

Layers are rendered by the GPU

Blessings of GPU Rendering


  • Fast graphics – layers are optimized for better use of graphical hardware
  • Percise animations – Core Animation allows for more animation control and complexity then UIKit
  • Rasterization control – layer content can be rasterized once and stored as bitmap

So Many Layers

https://github.com/scotteg/LayerPlayer

Honorable Mention: CAMetalLayer

Allows to use 3d rendering pipeline(shaders) in a layer

Animation

Simple Animation


							// Rotate view 180 degrees
							UIView.animate(
								withDuration: 0.25, // length of animation in seconds
								delay: 0.0, // animation start delay
								options: [.curveLinear], // timing curve
								animations: { // animation closure
									view.transform = view.transform.rotated(by: CGFloat(M_PI))},
								completion: { finished in // called after animation is complete
									view.transform = CGAffineTransform.identity
							})
						

Interpolates values of the view's properties

Uses shortest path between the values
(360° rotation does nothing!)

Animatable View Properties

  • frame
  • bounds
  • center
  • transform
  • alpha
  • backgroundColor
  • contentStretch

You should never animate position properties
of a view with Auto Layout cnstraints

Animation Options

Timing Curve – defines how fast values change

  • linear
  • easeIn
  • easeOut
  • easeInOut

Other Options

  • repeat – repeats animation ad infinum
  • allowUserInteractions – allows user interactions
  • beginFromCurrentState – animation started in the middle
    of another animation will use current values of a view

Keyframe Animation

Interpolates values between user defined keyframes


							// Rotate view 360 degrees
							let duration = 1.0
							UIView.animateKeyframes(
								withDuration: duration,
								delay: 0.0,
								options: [.calculationModeCubic], // interpolation method
								animations: {  /* define frames here */ }
							)
						

Keyframes


								// Rotate 360 degrees animations closure
								let frames = 6
								let angle = 2 * M_PI / Double(frames)
								let frameDuration = duration / Double(frames)
								for i in 0..<frames {
									UIView.addKeyframe(
										withRelativeStartTime: Double(i) * frameDuration,
										relativeDuration: frameDuration,
										animations: {
											sender.transform = sender.transform.rotated(by: CGFloat(angle))
									})
								}
						

Each keyframe defines an animation
with its own delay and duration

View Property Animator


								animator = UIViewPropertyAnimator(duration: 0, curve: .linear)
								{
									self.view.center = newLocation
									self.view.background-color = newColor
								}

								animator.fractionComplete = 0.5
						

Allows to dynamically modify animations before they finish

Avaliable since iOS 10

UIView Animation Limits

  • Animation is limited to view's properties
  • Doesn't allow custom timing functions
  • CPU capped rendering

Layer Animations

  • Processed on GPU
  • Shape Transitions
  • 3D Transformations
  • Clipping Animations

Animatable Layer Properties

anchorPoint backgroundColor backgroundFilters borderColor borderWidth bounds compositingFilter contents contentsRect cornerRadius doubleSided filters frame hidden mask masksToBounds opacity position shadowColor shadowOffset shadowOpacity shadowPath shadowRadius sublayers sublayerTransform transform zPosition

Any of the layer's properties can be animated

Basic Animation


							let fillAnimation = CABasicAnimation(keyPath: "fillColor")
							fillAnimation.fromValue = UIColor.clear.cgColor
							fillAnimation.toValue = UIColor.white.cgColor
							fillAnimation.duration = 1
							layer.add(fillAnimation, forKey: "fill animation")
						

Basic animation animates one property at a time

Group Animation


							end.beginTime = 1
							let groupAnimation = CAAnimationGroup()
							groupAnimation.animations = [start,end]
							groupAnimation.duration = 3
							circle.add(groupAnimation, forKey: "group stroke")
						

beginTime – sets the delay in the group animation

Keyframe Layer Animation


							let keyframeAnimation = CAKeyframeAnimation(keyPath: "path")
							keyframeAnimation.values = []
							for points in 5...10 {
							    let starPath = UIBezierPath(starWithNumberOfPoints: points, centeredAt: view.center, innerRadius: view.bounds.width / 6, outerRadius: view.bounds.width / 3)
							    keyframeAnimation.values?.append(starPath.cgPath)
							}
							keyframeAnimation.duration = 5
							keyframeAnimation.autoreverses = true
							star.add(keyframeAnimation, forKey: "points")
						

Processing Images

What is an Image?

  • UIImage – higher-level image object. Immutable, created from existing image content
  • CGImage – bitmap representation of an image. Mutable
  • CIImage – a "recipe" for an image, executed only when you request that an image be rendered for display or output

Image ⇄ Image


							cgImage = uiImage.cgImage // nil if uiImage contains ciImage reference
							ciImage = uiImage.ciImage // nil if uiImage contains ciImage reference ?
							uiImage = UIImage(ciImage: ciImage)
							uiImage = UIImage(cgImage: cgImage)
						

UIImage can display CIImage but to save it as a file
it has to be rendered first

Image Filtering

cat before
cat after
Filter is a piece of software that examines an input image pixel by pixel
and algorithmically applies some effect in order to create an output image

Discovering Filters


							CIFilter.filterNames(inCategories: nil) // all availiable
							CIFilter.filterNames(inCategory: category)
						

Core Image provides methods that let you query the system
for the available built-in filters

Filter Category

  • The type of effect produced by the filter
  • The usage of the filter (still image, video, etc)
  • Whether the filter is provided by Core Image

A filter can be a member of more than one category

Filter Configuration


							let filter = CIFilter(name: "CIThermal")
							filter.setValue(uiImage.ciImage!, forKey: kCIInputImageKey)
							if let output = filter.outputImage {
								let cgImage = CIContext().createCGImage(output, from: output.extent)
								uiImage = UIImage(cgImage: cgImage)
							}
						

There are some useful Swift wrappers for filters out there:
CIFilterKit

Filter Chaining


							let ciImage = CIImage(image: uiImage)
							let chainedResult = ciImage.applyingFilter("CIPhotoEffectProcess",
								withInputParameters: nil).applyingFilter("CIBloom",
									withInputParameters: [
										kCIInputRadiusKey: 10.0,
										kCIInputIntensityKey: 1.0
									])
						

Core Image combines chained filters into a single operation

Related Resources