Ilya Loshkarev loshkarev.i@gmail.com
SFEDU 2017
Context contains drawing parameters
and all information needed to render the paint
Graphical Contexts are organized in form of a stack
override func draw(_ rect: CGRect) {
// Get view's current graphics context
if let context = UIGraphicsGetCurrentContext() {
/* draw something */
}
}
// 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()
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
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()
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)
An offscreen context for reusing content
drawn with Core Graphics
let layer = CGLayer(context, size: CGSize(width: 100, height: 100), auxiliaryInfo: nil)
let layerContext = layer?.context
/* draw something */
context.draw(layer, at: CGPoint.zero)
An object that manages image-based content
and allows you to perform animations on that content
Completely unrelated to CGLayer
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
// 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!)
You should never animate position properties
of a view with Auto Layout cnstraints
Timing Curve – defines how fast values change
- linear
- easeIn
- easeOut
- easeInOut
repeat
– repeats animation ad infinumallowUserInteractions
– allows user interactions beginFromCurrentState
– animation started in the middle 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 */ }
)
// 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
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
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
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
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
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")
UIImage
– higher-level image object. Immutable, created from existing image contentCGImage
– bitmap representation of an image. MutableCIImage
– a "recipe" for an image, executed only when you request that an image be rendered for display or output
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
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
A filter can be a member of more than one category
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
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