Skip to content

Instantly share code, notes, and snippets.

@agibson73
Last active June 13, 2017 20:04
Show Gist options
  • Save agibson73/acd32b555d7bb0c31bcd0630a668bd6c to your computer and use it in GitHub Desktop.
Save agibson73/acd32b555d7bb0c31bcd0630a668bd6c to your computer and use it in GitHub Desktop.
Animated Text CGPath with an image background
import UIKit
import CoreMotion
@IBDesignable
class CutOutBluredText: UIView {
@IBInspectable var backgroundImage : UIImage? = nil{
didSet{
setUp()
}
}
@IBInspectable var text : String = ""{
didSet{
setupPathLayerWithText()
}
}
@IBInspectable var fontSize : CGFloat = 14{
didSet{
setUp()
}
}
@IBInspectable var font : UIFont = UIFont.systemFont(ofSize: 14){
didSet{
setUp()
}
}
@IBInspectable var shouldAnimateMovement : Bool = true{
didSet{
setUp()
}
}
@IBInspectable var shouldBlur : Bool = false{
didSet{
setUp()
}
}
@IBInspectable var shouldGradient : Bool = true{
didSet{
setUp()
}
}
@IBInspectable var gradientColors : [UIColor] = [.white,.darkGray]{
didSet{
setupPathLayerWithText()
}
}
@IBInspectable var gradientAlpha : CGFloat = 0.7{
didSet{
setupPathLayerWithText()
}
}
private var visualEffectView : UIView?
private var imageView : UIImageView?
private var manager : CMMotionManager?
private var radialGradientView : UIView?
private var radialGradientLayer : RadialGradientLayer?
override func awakeFromNib() {
super.awakeFromNib()
setUp()
}
override init(frame: CGRect) {
super.init(frame: frame)
setUp()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUp()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
}
func setUp(){
if imageView == nil{
imageView = UIImageView(frame: bounds)
imageView?.autoresizingMask = [.flexibleHeight,.flexibleWidth]
imageView?.contentMode = .scaleAspectFill
self.addSubview(imageView!)
}
imageView?.image = backgroundImage
if manager == nil && shouldAnimateMovement == true{
manager = CMMotionManager()
if (self.manager?.isAccelerometerAvailable)! {
self.manager?.accelerometerUpdateInterval = 0.06
self.manager?.startAccelerometerUpdates(to: .main) {
[weak self] (data: CMAccelerometerData?, error: Error?) in
if let acceleration = data?.acceleration {
if self?.imageView != nil{
let current = self?.imageView?.layer.position
let basicAnimation = CABasicAnimation(keyPath: "position")
basicAnimation.toValue = NSValue(cgPoint:CGPoint(x: (current?.x)! - CGFloat(acceleration.x * 100), y: (current?.y)! + CGFloat(acceleration.x * 100)))
basicAnimation.duration = 0.3
self?.imageView?.layer.add(basicAnimation, forKey: nil)
}
}
}
}else{
print("not available or on")
}
}
if visualEffectView == nil && shouldBlur == true{
let visualEffects = UIBlurEffect(style: .light)
visualEffectView = UIVisualEffectView(effect: visualEffects)
visualEffectView?.frame = self.bounds
visualEffectView?.autoresizingMask = [.flexibleWidth,.flexibleHeight]
self.addSubview(visualEffectView!)
}
setupPathLayerWithText()
}
private func setupPathLayerWithText() {
layer.mask = nil
let letters = CGMutablePath()
let font = CTFontCreateWithName(self.font.fontName as CFString, fontSize, nil)
let attrString = NSAttributedString(string: text, attributes: [kCTFontAttributeName as String : font])
let line = CTLineCreateWithAttributedString(attrString)
let runArray = CTLineGetGlyphRuns(line)
for runIndex in 0..<CFArrayGetCount(runArray) {
let run : CTRun = unsafeBitCast(CFArrayGetValueAtIndex(runArray, runIndex), to: CTRun.self)
let dictRef : CFDictionary = CTRunGetAttributes(run)
let dict : NSDictionary = dictRef as NSDictionary
let runFont = dict[kCTFontAttributeName as String] as! CTFont
for runGlyphIndex in 0..<CTRunGetGlyphCount(run) {
let thisGlyphRange = CFRangeMake(runGlyphIndex, 1)
var glyph = CGGlyph()
var position = CGPoint.zero
CTRunGetGlyphs(run, thisGlyphRange, &glyph)
CTRunGetPositions(run, thisGlyphRange, &position)
let letter = CTFontCreatePathForGlyph(runFont, glyph, nil)
let t = CGAffineTransform(translationX: position.x, y: position.y)
letters.addPath(letter!, transform:t)
}
}
let path = UIBezierPath()
path.move(to: .zero)
path.append(UIBezierPath(cgPath: letters))
if path.bounds.width > self.bounds.width || path.bounds.height > self.bounds.height{
self.fontSize = self.fontSize - 1
setupPathLayerWithText()
return
}
let pathLayer = CAShapeLayer()
pathLayer.frame = bounds;
pathLayer.bounds = path.cgPath.boundingBox
pathLayer.isGeometryFlipped = true
pathLayer.path = path.cgPath
pathLayer.strokeColor = UIColor.white.withAlphaComponent(0.9).cgColor
pathLayer.fillColor = UIColor.white.cgColor
pathLayer.lineWidth = 1.0
pathLayer.lineJoin = kCALineJoinBevel
pathLayer.allowsEdgeAntialiasing = true
self.layer.mask = pathLayer
if shouldGradient == true && radialGradientView == nil{
radialGradientView = UIView(frame: bounds)
radialGradientLayer = RadialGradientLayer()
radialGradientView?.autoresizingMask = [.flexibleWidth,.flexibleHeight]
radialGradientLayer?.frame = bounds
radialGradientView?.layer.addSublayer(radialGradientLayer!)
self.insertSubview(radialGradientView!, aboveSubview: imageView!)
}
radialGradientView?.alpha = gradientAlpha
radialGradientLayer?.colors = gradientColors
setNeedsDisplay()
}
deinit {
manager?.stopAccelerometerUpdates()
}
override func layoutSubviews() {
super.layoutSubviews()
setupPathLayerWithText()
if shouldGradient == true && radialGradientView != nil{
for la in (radialGradientView?.layer.sublayers)!{
la.frame = bounds
}
}
}
}
import UIKit
class RadialGradientLayer: CALayer {
required override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
required override init(layer: Any) {
super.init(layer: layer)
}
public var colors = [UIColor.white, UIColor.darkGray]{
didSet{
setNeedsDisplay()
}
}
override func draw(in ctx: CGContext) {
ctx.saveGState()
let colorSpace = CGColorSpaceCreateDeviceRGB()
var locations = [CGFloat]()
for i in 0...colors.count-1 {
locations.append(CGFloat(i) / CGFloat(colors.count))
}
let convertedColors = colors.map({$0.cgColor})
let gradient = CGGradient(colorsSpace: colorSpace, colors: convertedColors as CFArray, locations: locations)
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radius = min(bounds.midX, bounds.midY)
ctx.drawRadialGradient(gradient!, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: radius, options: CGGradientDrawingOptions(rawValue: 0))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment