Last active
March 24, 2023 20:09
-
-
Save mikolasstuchlik/100e38f3a92bb92a01004abf983f0d9c to your computer and use it in GitHub Desktop.
UILabel subclass.
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 | |
/// `Label` is an `UILabel` subclass that simplifies line height and letter spacing manipulation. | |
public final class Label: UILabel { | |
/// Since line spacing is always absolute in iOS, we need to compute the correct line spacing for given | |
/// font and scale. | |
public enum LineHeight { | |
case absoluteSpacing(CGFloat) | |
case fraction(CGFloat) | |
func lineSpacing(for font: UIFont) -> CGFloat { | |
switch self { | |
case .absoluteSpacing(let value): | |
return value | |
case .fraction(let value): | |
return value * font.pointSize - font.pointSize | |
} | |
} | |
} | |
/// Set to `true` in case you want to use dynamic scaling. Default `false`. | |
public var scaleFont = false { | |
didSet { | |
reformatAndStore(string: stylableText) | |
} | |
} | |
/// Set a non-nil value in case you want to use custom letter spacing. | |
public var letterSpacing: CGFloat? { | |
didSet { | |
reformatAndStore(string: stylableText) | |
} | |
} | |
/// Set a non-nil value in case you want to use custom line height. | |
public var lineHeight: LineHeight? { | |
didSet { | |
reformatAndStore(string: stylableText) | |
} | |
} | |
/// Font of the label, replacing the `font` property. Do not pass a scaled font. | |
public var unscaledFont: UIFont = .systemFont(ofSize: UIFont.systemFontSize) { | |
didSet { | |
reformatAndStore(string: stylableText) | |
} | |
} | |
public var additionalAttributes: [([NSAttributedString.Key : Any], Range<String.Index>)] = [] { | |
didSet { | |
reformatAndStore(string: stylableText) | |
} | |
} | |
/// The text if the label, replaces the `text` and `attributedText` properties. | |
/// | |
/// It is not possible in the current implementation to display an attributed string. | |
public var stylableText: String? { | |
get { | |
super.attributedText?.string | |
} | |
set { | |
additionalAttributes = [] | |
reformatAndStore(string: newValue) | |
} | |
} | |
@available(*, unavailable, message: "This property is not available for Label, use 'unscaledFont'.") | |
override public var font: UIFont! { | |
get { super.font } | |
set { super.font = newValue } | |
} | |
@available(*, unavailable, message: "This property is not available for Label, use 'stylableText'.") | |
override public var text: String? { | |
get { super.text } | |
set { super.text = newValue } | |
} | |
@available(*, unavailable, message: "This property is not available for Label, use 'stylableText'.") | |
override public var attributedText: NSAttributedString? { | |
get { super.attributedText } | |
set { super.attributedText = newValue } | |
} | |
// Observing is required so font scales correctly | |
override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { | |
super.traitCollectionDidChange(previousTraitCollection) | |
reformatAndStore(string: stylableText) | |
} | |
@objc | |
private func buttonShapesEnabledStatusDidChange() { | |
reformatAndStore(string: stylableText) | |
} | |
private func reformatAndStore(string: String?) { | |
super.attributedText = string.flatMap { text in | |
var attrStr = NSMutableAttributedString(string: text, attributes: attributes) | |
additionalAttributes.forEach { (custom, range) in | |
attrStr.addAttributes(custom, range: NSRange(range, in: text)) | |
} | |
return attrStr | |
} | |
} | |
private var attributes: [NSAttributedString.Key: Any] { | |
var attributes = [NSAttributedString.Key: Any]() | |
let designatedFont = scaleFont ? unscaledFont : UIFontMetrics.default.scaledFont(for: unscaledFont) | |
super.font = designatedFont | |
attributes[.font] = designatedFont | |
if let letterSpacing = letterSpacing { | |
attributes[.kern] = letterSpacing | |
} | |
let paragraphStyle = NSMutableParagraphStyle() | |
paragraphStyle.alignment = textAlignment | |
paragraphStyle.lineBreakMode = .byTruncatingTail | |
if let lineHeight = lineHeight { | |
paragraphStyle.lineSpacing = lineHeight.lineSpacing(for: designatedFont) | |
} | |
attributes[.paragraphStyle] = paragraphStyle | |
return attributes | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment