Last active
January 25, 2018 18:31
-
-
Save chrislconover/66aae601888d2339ccd762f210bed1b5 to your computer and use it in GitHub Desktop.
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
// Adapted from original Apple Obj-C sample code | |
import UIKit | |
import CoreText | |
import QuartzCore | |
@IBDesignable | |
class ClippingLabel : UILabel { | |
override var text:String? { didSet { | |
if self.text != oldValue { invalidateIntrinsicContentSize() }}} | |
override var font:UIFont! { didSet { | |
if self.font != oldValue { invalidateIntrinsicContentSize() }}} | |
override func intrinsicContentSize() -> CGSize { | |
guard let text = text else { return CGSizeZero } | |
let attributed = NSAttributedString(string: text, | |
attributes: [NSFontAttributeName:font]) | |
return CTLineGetImageBounds(CTLineCreateWithAttributedString(attributed), nil).size | |
} | |
} | |
@IBDesignable | |
public class ScalingLabel : UIView { | |
public override init(frame: CGRect) { | |
super.init(frame: frame) | |
} | |
required public init?(coder aDecoder: NSCoder) { | |
super.init(coder: aDecoder) | |
} | |
// MARK: configuration | |
public var font:UIFont? = UIFont.systemFontOfSize(12) | |
@IBInspectable public var text:String? { | |
get { return attributed?.string } | |
set { guard let t = newValue, f = self.font else { self.attributed = nil; return } | |
self.attributed = NSAttributedString(string: t, attributes: [NSFontAttributeName:f]) }} | |
@IBInspectable public var attributed:NSAttributedString? { | |
get { return self.textLayer.text } | |
set { self.textLayer.text = newValue; invalidateIntrinsicContentSize() }} | |
// MARK: layout | |
override public func layoutSubviews() { | |
super.layoutSubviews() | |
self.textLayer.frame = self.layer.bounds | |
invalidateIntrinsicContentSize() | |
} | |
public override func intrinsicContentSize() -> CGSize { | |
return textLayer.contentFrame.size | |
} | |
// private var textLayer:ScalableTextLayer { return self.layer as! ScalableTextLayer } | |
lazy var textLayer:ScalableTextLayer = { | |
let layer = ScalableTextLayer() | |
self.layer.addSublayer(layer) | |
return layer | |
}() | |
} | |
extension CTFont : Hashable, CustomStringConvertible { | |
public var hashValue: Int { | |
return self.description.hashValue | |
} | |
public var description:String { | |
return (CTFontCopyName(self, kCTFontUniqueNameKey) as? NSString as? String) ?? "" | |
} | |
} | |
public func == (left: CTFont, right: CTFont) -> Bool { | |
return left.hashValue == right.hashValue | |
} | |
public class ScalableTextLayer : CALayer { | |
typealias CTDictionary = [String:AnyObject] | |
public var text: NSAttributedString? { didSet { | |
line = self.text != nil ? CTLineCreateWithAttributedString(self.text!) : nil | |
generatePaths() }} | |
public var color:CGColor? | |
public var zoomToFit:Bool = true { didSet { self.setNeedsLayout() }} | |
public var centerContent:Bool = true { didSet { self.setNeedsLayout() }} | |
public private(set) var contentFrame:CGRect = CGRectZero | |
public override init() { | |
super.init() | |
geometryFlipped = true | |
} | |
public required init?(coder aDecoder: NSCoder) { | |
super.init(coder: aDecoder) | |
geometryFlipped = true | |
} | |
func generatePaths() { | |
CATransaction.begin() | |
CATransaction.setDisableActions(true) | |
// clear existing layers, and add new layers as required | |
for layer in layers { layer.path = nil } | |
if let line = self.line { | |
// add new layers as required | |
var glyphInLine = 0 | |
let glyphCount = CTLineGetGlyphCount(self.line) | |
for _ in layers.count..<glyphCount { layers.append(createShapeLayer()) } | |
let offset = CTLineGetImageBounds(line, nil).origin | |
// for each run | |
let runs = CTLineGetGlyphRuns(line) as [AnyObject] as! [CTRun] | |
for (runIndex, run) in runs.enumerate() { | |
// get font | |
let attributes = CTRunGetAttributes(run) as NSDictionary as! [String:AnyObject] | |
let runFont:CTFont = attributes[kCTFontAttributeName as String] as! CTFont | |
let style = styleFromAttributes(attributes) | |
// get glyphs for current run | |
let glyphCount = CTRunGetGlyphCount(run) | |
let range = CFRangeMake(0, glyphCount) | |
var glyphs = Array<CGGlyph>(count: glyphCount, repeatedValue: CGGlyph()) | |
var positions = [CGPoint](count: glyphCount, repeatedValue: CGPointZero) | |
CTRunGetGlyphs(run, range, &glyphs) | |
CTRunGetPositions(run, range, &positions) | |
// for each glyph in run | |
for (i, glyph) in glyphs.enumerate() { | |
// get next glyph | |
let layer = layers[glyphInLine] | |
glyphInLine += 1 | |
layer.style = style | |
layer.path = pathForGlyph(glyph, withFont: runFont) | |
layer.name = "Font \(runFont), run: \(runIndex), glyph \(i): \(glyph)" | |
var position = positions[i] | |
position = CGPoint(x: position.x - offset.x, y: position.y - offset.y) | |
layer.position = position | |
layer.backgroundColor = nil | |
// let glyphFrame = withUnsafePointer(&glyph) { pointer -> CGRect in | |
// return CTFontGetBoundingRectsForGlyphs(runFont, .Default, pointer, nil, 1) } | |
// | |
// let boundingLayer = CALayer() | |
// boundingLayer.frame = glyphFrame | |
// boundingLayer.borderColor = UIColor.greenColor().CGColor | |
// boundingLayer.borderWidth = 0.1 | |
// layer.addSublayer(boundingLayer) | |
} | |
} | |
CATransaction.commit() | |
self.setNeedsLayout() | |
} | |
func styleFromAttributes(attributes: [String:AnyObject]) -> [String:AnyObject] { | |
// return fill color, if specified | |
return ["fillColor": color | |
?? attributes[NSForegroundColorAttributeName] | |
?? UIColor.blackColor().CGColor] | |
} | |
func pathForGlyph(glyph:CGGlyph, withFont font: CTFont) -> CGPath? { | |
// case of existing font entry | |
if var glyphToPath = ScalableTextLayer.fontToGlyphToPath[font] { | |
let cachePath: (CGGlyph, CTFont) -> CGPath? = { glyph, font in | |
let path = CTFontCreatePathForGlyph(font, glyph, nil) | |
glyphToPath[glyph] = path | |
return path } | |
return glyphToPath[glyph] ?? cachePath(glyph, font) | |
} | |
// case of missing font, create and return font/path | |
else { | |
if let path = CTFontCreatePathForGlyph(font, glyph, nil) { | |
ScalableTextLayer.fontToGlyphToPath[font] = [glyph:path] | |
return path | |
} | |
return nil | |
} | |
} | |
override public func layoutSublayers() { | |
if let line = self.line { | |
self.sublayers!.count | |
var transform = CATransform3DIdentity | |
// if we are to scale | |
if zoomToFit { | |
let lineBounds = CTLineGetImageBounds(line, nil) | |
let lineWidth = lineBounds.size.width | |
let lineHeight = lineBounds.size.height | |
// layer geometry | |
let bounds = self.bounds | |
let anchorPoint = self.anchorPoint | |
let scaleX = bounds.size.width / lineWidth | |
let scaleY = bounds.size.height / lineHeight | |
let anchorX = bounds.origin.x + bounds.size.width * anchorPoint.x; | |
let anchorY = bounds.origin.y + bounds.size.height * anchorPoint.y; | |
// translate to origin so that we can properly scale | |
transform = CATransform3DTranslate(transform, -anchorX, -anchorY, 0.0) | |
// // case of smaller x scale | |
// if scaleX < scaleY { | |
// transform = CATransform3DScale(transform, scaleX, scaleX, 1.0) | |
// if centerContent { | |
// let ty = ((bounds.size.height / 2.0) / scaleX) - lineHeight / 2.0 | |
// transform = CATransform3DTranslate(transform, 0.0, ty, 0.0) | |
// } | |
// } | |
// case of smaller y scale | |
// else { | |
transform = CATransform3DScale(transform, scaleY, scaleY, 1.0) | |
if centerContent { | |
let tx = ((bounds.size.width / 2.0) / scaleY) - lineWidth / 2.0 | |
transform = CATransform3DTranslate(transform, tx, 0, 0.0) | |
} | |
// update content frame to reflect curtains or letterboxing | |
contentFrame = CGRectMake(lineBounds.origin.x * scaleY, lineBounds.origin.y * scaleY, | |
lineBounds.size.width * scaleY, lineBounds.size.height * scaleY) | |
// } | |
// translate back to original anchor point | |
transform = CATransform3DTranslate(transform, anchorX, anchorY, 0.0) | |
} | |
// apply tranform to all sub layers | |
CATransaction.begin() | |
CATransaction.setDisableActions(true) | |
self.sublayerTransform = transform | |
CATransaction.commit() | |
} | |
} | |
func createShapeLayer() -> CAShapeLayer { | |
let layer = CAShapeLayer() | |
self.addSublayer(layer) | |
layer.geometryFlipped = false | |
return layer | |
} | |
var line: CTLine! | |
var layers: [CAShapeLayer!] = [] | |
typealias GlyphToPath = Dictionary<CGGlyph, CGPath> | |
typealias FontToGlyphToPath = Dictionary<CTFont, GlyphToPath> | |
static var fontToGlyphToPath = FontToGlyphToPath() | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment