Last active
June 13, 2017 20:04
-
-
Save agibson73/acd32b555d7bb0c31bcd0630a668bd6c to your computer and use it in GitHub Desktop.
Animated Text CGPath with an image background
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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