Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import UIKit
- class ExpandableLabel: UIView {
- // MARK: - UI Components
- private let label: UILabel = {
- let lbl = UILabel()
- lbl.numberOfLines = 2
- lbl.lineBreakMode = .byTruncatingTail
- lbl.font = UIFont.systemFont(ofSize: UIFont.labelFontSize)
- return lbl
- }()
- private let button: UIButton = {
- let btn = UIButton(type: .system)
- btn.setTitle("Show More", for: .normal)
- btn.isHidden = true
- return btn
- }()
- private let stackView: UIStackView = {
- let sv = UIStackView()
- sv.axis = .vertical
- sv.spacing = 4
- return sv
- }()
- // MARK: - Properties
- var text: String? {
- didSet {
- label.text = text
- resetState()
- }
- }
- private var isExpanded = false
- // MARK: - Initialization
- override init(frame: CGRect) {
- super.init(frame: frame)
- commonInit()
- }
- required init?(coder: NSCoder) {
- super.init(coder: coder)
- commonInit()
- }
- private func commonInit() {
- setupLayout()
- button.addTarget(self, action: #selector(handleButtonTap), for: .touchUpInside)
- }
- // MARK: - Layout
- private func setupLayout() {
- addSubview(stackView)
- stackView.translatesAutoresizingMaskIntoConstraints = false
- NSLayoutConstraint.activate([
- stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
- stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
- stackView.topAnchor.constraint(equalTo: topAnchor),
- stackView.bottomAnchor.constraint(equalTo: bottomAnchor)
- ])
- stackView.addArrangedSubview(label)
- stackView.addArrangedSubview(button)
- }
- override func layoutSubviews() {
- super.layoutSubviews()
- updateButtonVisibility()
- }
- // MARK: - State Management
- private func resetState() {
- isExpanded = false
- label.numberOfLines = 2
- setNeedsLayout()
- }
- private func updateButtonVisibility() {
- guard !isExpanded else {
- button.isHidden = true
- return
- }
- button.isHidden = !isTextTruncated()
- }
- private func isTextTruncated() -> Bool {
- guard let text = label.text, !text.isEmpty else { return false }
- let layoutManager = NSLayoutManager()
- let textContainer = NSTextContainer(size: CGSize(
- width: label.bounds.width,
- height: .greatestFiniteMagnitude
- ))
- let textStorage = NSTextStorage(
- string: text,
- attributes: [.font: label.font!]
- )
- layoutManager.addTextContainer(textContainer)
- textStorage.addLayoutManager(layoutManager)
- textContainer.lineFragmentPadding = 0
- textContainer.lineBreakMode = label.lineBreakMode
- textContainer.maximumNumberOfLines = label.numberOfLines
- let numberOfGlyphs = layoutManager.numberOfGlyphs
- var numberOfLines = 0
- var index = 0
- var lineRange = NSRange(location: 0, length: 0)
- while index < numberOfGlyphs {
- layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
- index = NSMaxRange(lineRange)
- numberOfLines += 1
- if numberOfLines > label.numberOfLines {
- return true
- }
- }
- return false
- }
- // MARK: - Action Handling
- @objc private func handleButtonTap() {
- isExpanded = true
- label.numberOfLines = 0
- button.isHidden = true
- }
- }
- // usage
- let expandableLabel = ExpandableLabel()
- expandableLabel.text = "Your long text goes here..."
- expandableLabel.translatesAutoresizingMaskIntoConstraints = false
- // Add to view and set constraints
- view.addSubview(expandableLabel)
- NSLayoutConstraint.activate([
- expandableLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
- expandableLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
- expandableLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
- ])
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement