Advertisement
Milotronik

Expandable label uikit

Feb 14th, 2025
104
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Swift 4.33 KB | None | 0 0
  1. import UIKit
  2.  
  3. class ExpandableLabel: UIView {
  4.     // MARK: - UI Components
  5.     private let label: UILabel = {
  6.         let lbl = UILabel()
  7.         lbl.numberOfLines = 2
  8.         lbl.lineBreakMode = .byTruncatingTail
  9.         lbl.font = UIFont.systemFont(ofSize: UIFont.labelFontSize)
  10.         return lbl
  11.     }()
  12.    
  13.     private let button: UIButton = {
  14.         let btn = UIButton(type: .system)
  15.         btn.setTitle("Show More", for: .normal)
  16.         btn.isHidden = true
  17.         return btn
  18.     }()
  19.    
  20.     private let stackView: UIStackView = {
  21.         let sv = UIStackView()
  22.         sv.axis = .vertical
  23.         sv.spacing = 4
  24.         return sv
  25.     }()
  26.    
  27.     // MARK: - Properties
  28.     var text: String? {
  29.         didSet {
  30.             label.text = text
  31.             resetState()
  32.         }
  33.     }
  34.    
  35.     private var isExpanded = false
  36.    
  37.     // MARK: - Initialization
  38.     override init(frame: CGRect) {
  39.         super.init(frame: frame)
  40.         commonInit()
  41.     }
  42.    
  43.     required init?(coder: NSCoder) {
  44.         super.init(coder: coder)
  45.         commonInit()
  46.     }
  47.    
  48.     private func commonInit() {
  49.         setupLayout()
  50.         button.addTarget(self, action: #selector(handleButtonTap), for: .touchUpInside)
  51.     }
  52.    
  53.     // MARK: - Layout
  54.     private func setupLayout() {
  55.         addSubview(stackView)
  56.         stackView.translatesAutoresizingMaskIntoConstraints = false
  57.        
  58.         NSLayoutConstraint.activate([
  59.             stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
  60.             stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
  61.             stackView.topAnchor.constraint(equalTo: topAnchor),
  62.             stackView.bottomAnchor.constraint(equalTo: bottomAnchor)
  63.         ])
  64.        
  65.         stackView.addArrangedSubview(label)
  66.         stackView.addArrangedSubview(button)
  67.     }
  68.    
  69.     override func layoutSubviews() {
  70.         super.layoutSubviews()
  71.         updateButtonVisibility()
  72.     }
  73.    
  74.     // MARK: - State Management
  75.     private func resetState() {
  76.         isExpanded = false
  77.         label.numberOfLines = 2
  78.         setNeedsLayout()
  79.     }
  80.    
  81.     private func updateButtonVisibility() {
  82.         guard !isExpanded else {
  83.             button.isHidden = true
  84.             return
  85.         }
  86.        
  87.         button.isHidden = !isTextTruncated()
  88.     }
  89.    
  90.     private func isTextTruncated() -> Bool {
  91.         guard let text = label.text, !text.isEmpty else { return false }
  92.        
  93.         let layoutManager = NSLayoutManager()
  94.         let textContainer = NSTextContainer(size: CGSize(
  95.             width: label.bounds.width,
  96.             height: .greatestFiniteMagnitude
  97.         ))
  98.         let textStorage = NSTextStorage(
  99.             string: text,
  100.             attributes: [.font: label.font!]
  101.         )
  102.        
  103.         layoutManager.addTextContainer(textContainer)
  104.         textStorage.addLayoutManager(layoutManager)
  105.        
  106.         textContainer.lineFragmentPadding = 0
  107.         textContainer.lineBreakMode = label.lineBreakMode
  108.         textContainer.maximumNumberOfLines = label.numberOfLines
  109.        
  110.         let numberOfGlyphs = layoutManager.numberOfGlyphs
  111.         var numberOfLines = 0
  112.         var index = 0
  113.         var lineRange = NSRange(location: 0, length: 0)
  114.        
  115.         while index < numberOfGlyphs {
  116.             layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
  117.             index = NSMaxRange(lineRange)
  118.             numberOfLines += 1
  119.             if numberOfLines > label.numberOfLines {
  120.                 return true
  121.             }
  122.         }
  123.        
  124.         return false
  125.     }
  126.    
  127.     // MARK: - Action Handling
  128.     @objc private func handleButtonTap() {
  129.         isExpanded = true
  130.         label.numberOfLines = 0
  131.         button.isHidden = true
  132.     }
  133. }
  134.  
  135.  
  136. // usage
  137.  
  138. let expandableLabel = ExpandableLabel()
  139. expandableLabel.text = "Your long text goes here..."
  140. expandableLabel.translatesAutoresizingMaskIntoConstraints = false
  141.  
  142. // Add to view and set constraints
  143. view.addSubview(expandableLabel)
  144.  
  145. NSLayoutConstraint.activate([
  146.     expandableLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
  147.     expandableLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
  148.     expandableLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
  149. ])
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement