Files
Lightbox/Source/Views/InfoLabel.swift
T

153 lines
3.9 KiB
Swift

import UIKit
public protocol InfoLabelDelegate: class {
func infoLabel(_ infoLabel: InfoLabel, didExpand expanded: Bool)
}
open class InfoLabel: UILabel {
lazy var tapGestureRecognizer: UITapGestureRecognizer = { [unowned self] in
let gesture = UITapGestureRecognizer()
gesture.addTarget(self, action: #selector(labelDidTap(_:)))
return gesture
}()
open var numberOfVisibleLines = 2
var ellipsis: String {
return "... \(LightboxConfig.InfoLabel.ellipsisText)"
}
open weak var delegate: InfoLabelDelegate?
fileprivate var shortText = ""
var fullText: String {
didSet {
shortText = truncatedText
updateText(fullText)
configureLayout()
}
}
var expandable: Bool {
return shortText != fullText
}
fileprivate(set) var expanded = false {
didSet {
delegate?.infoLabel(self, didExpand: expanded)
}
}
var truncatedText: String {
var truncatedText = fullText
guard numberOfLines(fullText) > numberOfVisibleLines else {
return truncatedText
}
// Perform quick "rough cut"
while numberOfLines(truncatedText) > numberOfVisibleLines * 2 {
truncatedText = String(truncatedText.prefix(truncatedText.count / 2))
}
// Capture the endIndex of truncatedText before appending ellipsis
var truncatedTextCursor = truncatedText.endIndex
truncatedText += ellipsis
// Remove characters ahead of ellipsis until the text is the right number of lines
while numberOfLines(truncatedText) > numberOfVisibleLines {
// To avoid "Cannot decrement before startIndex"
guard truncatedTextCursor > truncatedText.startIndex else {
break
}
truncatedTextCursor = truncatedText.index(before: truncatedTextCursor)
truncatedText.remove(at: truncatedTextCursor)
}
return truncatedText
}
// MARK: - Initialization
public init(text: String, expanded: Bool = false) {
self.fullText = text
super.init(frame: CGRect.zero)
numberOfLines = 0
updateText(text)
self.expanded = expanded
addGestureRecognizer(tapGestureRecognizer)
}
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Actions
@objc func labelDidTap(_ tapGestureRecognizer: UITapGestureRecognizer) {
shortText = truncatedText
expanded ? collapse() : expand()
}
func expand() {
frame.size.height = heightForString(fullText)
updateText(fullText)
expanded = expandable
}
func collapse() {
frame.size.height = heightForString(shortText)
updateText(shortText)
expanded = false
}
fileprivate func updateText(_ string: String) {
let textAttributes = LightboxConfig.InfoLabel.textAttributes
let attributedString = NSMutableAttributedString(string: string, attributes: textAttributes)
if let range = string.range(of: ellipsis) {
let ellipsisColor = LightboxConfig.InfoLabel.ellipsisColor
let ellipsisRange = NSRange(range, in: string)
attributedString.addAttribute(.foregroundColor, value: ellipsisColor, range: ellipsisRange)
}
attributedText = attributedString
}
// MARK: - Helper methods
fileprivate func heightForString(_ string: String) -> CGFloat {
return string.boundingRect(
with: CGSize(width: bounds.size.width, height: CGFloat.greatestFiniteMagnitude),
options: [.usesLineFragmentOrigin, .usesFontLeading],
attributes: [NSAttributedStringKey.font: font],
context: nil).height
}
fileprivate func numberOfLines(_ string: String) -> Int {
let lineHeight = "A".size(withAttributes: [NSAttributedStringKey.font: font]).height
let totalHeight = heightForString(string)
return Int(totalHeight / lineHeight)
}
}
// MARK: - LayoutConfigurable
extension InfoLabel: LayoutConfigurable {
@objc public func configureLayout() {
shortText = truncatedText
expanded ? expand() : collapse()
}
}