Created
November 7, 2017 12:09
-
-
Save BjornRuud/38a43836ec22bdf9e1b0bebdb4f8b38a to your computer and use it in GitHub Desktop.
Debug mode for UILabel that can optionally show bounds, ascender, descender, x height, cap height, baseline and leading.
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 | |
struct UILabelDebugOptions: OptionSet { | |
let rawValue: Int | |
static let bounds = UILabelDebugOptions(rawValue: 1 << 0) | |
static let ascender = UILabelDebugOptions(rawValue: 1 << 1) | |
static let descender = UILabelDebugOptions(rawValue: 1 << 2) | |
static let xHeight = UILabelDebugOptions(rawValue: 1 << 3) | |
static let capHeight = UILabelDebugOptions(rawValue: 1 << 4) | |
static let baseLine = UILabelDebugOptions(rawValue: 1 << 5) | |
static let leading = UILabelDebugOptions(rawValue: 1 << 6) | |
static let all: UILabelDebugOptions = [.bounds, .ascender, .descender, .xHeight, .capHeight, .baseLine, .leading] | |
} | |
extension UILabel { | |
func showDebugInfo(options: UILabelDebugOptions = [.all]) { | |
if let debugView = subviews.first(where: { $0.tag == debugViewTag }) as? UILabelDebugView { | |
debugView.options = options | |
return | |
} | |
let debugView = UILabelDebugView(label: self) | |
debugView.options = options | |
debugView.tag = debugViewTag | |
debugView.translatesAutoresizingMaskIntoConstraints = false | |
addSubview(debugView) | |
let equalWidth = NSLayoutConstraint(item: debugView, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1, constant: 0) | |
let equalHeight = NSLayoutConstraint(item: debugView, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 1, constant: 0) | |
let centerX = NSLayoutConstraint(item: debugView, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0) | |
let centerY = NSLayoutConstraint(item: debugView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0) | |
NSLayoutConstraint.activate([equalWidth, equalHeight, centerX, centerY]) | |
} | |
func hideDebugInfo() { | |
if let foundView = subviews.first(where: { $0.tag == debugViewTag }) as? UILabelDebugView { | |
foundView.removeFromSuperview() | |
} | |
} | |
} | |
private let debugViewTag = 0xbeef | |
private final class UILabelDebugView: UIView { | |
weak var label: UILabel? | |
var options: UILabelDebugOptions = [.all] | |
init(label: UILabel) { | |
self.label = label | |
super.init(frame: label.bounds) | |
self.backgroundColor = .clear | |
// If this is not set, frame changes will _not_ call draw(rect:) | |
self.contentMode = .redraw | |
} | |
required init?(coder aDecoder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
override func draw(_ rect: CGRect) { | |
guard let label = self.label, let ctx = UIGraphicsGetCurrentContext() else { | |
return | |
} | |
// Find largest font attribute | |
var font: UIFont = label.font | |
if let attributedText = label.attributedText { | |
attributedText.enumerateAttribute(.font, in: NSRange(location: 0, length: attributedText.length)) { (object, _, _) in | |
guard let foundFont = object as? UIFont else { | |
return | |
} | |
if foundFont.pointSize > font.pointSize { | |
font = foundFont | |
} | |
} | |
} | |
let bounds = self.bounds | |
let lineWidth: CGFloat = 1 | |
let halfLineWidth = lineWidth / 2 | |
// Background | |
if let bgColor = backgroundColor { | |
ctx.setFillColor(bgColor.cgColor) | |
ctx.fill(bounds) | |
} else { | |
ctx.clear(bounds) | |
} | |
// Baseline | |
if options.contains(.baseLine) { | |
ctx.setFillColor(UIColor.red.cgColor) | |
let baselineRect = CGRect(x: 0, y: font.ascender - halfLineWidth, width: bounds.width, height: lineWidth) | |
ctx.fill(baselineRect) | |
} | |
// Cap height | |
if options.contains(.capHeight) { | |
ctx.setFillColor(UIColor.green.cgColor) | |
let capHeightRect = CGRect(x: 0, y: font.ascender - font.capHeight - halfLineWidth, width: bounds.width, height: lineWidth) | |
ctx.fill(capHeightRect) | |
} | |
// X height | |
if options.contains(.xHeight) { | |
ctx.setFillColor(UIColor.green.cgColor) | |
let xHeightRect = CGRect(x: 0, y: font.ascender - font.xHeight - halfLineWidth, width: bounds.width, height: lineWidth) | |
ctx.fill(xHeightRect) | |
} | |
// Ascender | |
if options.contains(.ascender) { | |
ctx.setFillColor(UIColor.green.cgColor) | |
let ascenderRect = CGRect(x: 0, y: 0 - halfLineWidth, width: bounds.width, height: lineWidth) | |
ctx.fill(ascenderRect) | |
} | |
// Descender | |
if options.contains(.descender) { | |
ctx.setFillColor(UIColor.green.cgColor) | |
let descenderRect = CGRect(x: 0, y: font.ascender - font.descender - halfLineWidth, width: bounds.width, height: lineWidth) | |
ctx.fill(descenderRect) | |
} | |
// Leading | |
if options.contains(.leading) && font.leading > 0 { | |
ctx.setFillColor(UIColor.green.cgColor) | |
let leadingRect = CGRect(x: 0, y: font.ascender - font.descender + font.leading - halfLineWidth, width: bounds.width, height: lineWidth) | |
ctx.fill(leadingRect) | |
} | |
// Bounds | |
if options.contains(.bounds) { | |
ctx.setStrokeColor(UIColor.blue.cgColor) | |
ctx.setLineWidth(lineWidth) | |
ctx.stroke(bounds) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment