Skip to content

Instantly share code, notes, and snippets.

@BjornRuud
Created November 7, 2017 12:09
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save BjornRuud/38a43836ec22bdf9e1b0bebdb4f8b38a to your computer and use it in GitHub Desktop.
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.
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