// a replacement for UILabel class TextViewLabel: UITextView { public var numberOfLines: Int = 0 { didSet { textContainer.maximumNumberOfLines = numberOfLines } } public var lineBreakMode: NSLineBreakMode = .byTruncatingTail { didSet { textContainer.lineBreakMode = lineBreakMode } } override init(frame: CGRect, textContainer: NSTextContainer?) { super.init(frame: frame, textContainer: textContainer) commonInit() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) commonInit() } private func commonInit() -> Void { isScrollEnabled = false isEditable = false isSelectable = false textContainerInset = UIEdgeInsets.zero textContainer.lineFragmentPadding = 0 } } class ViewController: UIViewController { let labelStackView = UIStackView() let textViewStackView = UIStackView() override func viewDidLoad() { super.viewDidLoad() let scrollView = UIScrollView() let hStack = UIStackView() hStack.axis = .horizontal hStack.spacing = 8 hStack.distribution = .fillEqually hStack.alignment = .top [labelStackView, textViewStackView].forEach { v in v.axis = .vertical v.spacing = 4 hStack.addArrangedSubview(v) } hStack.translatesAutoresizingMaskIntoConstraints = false scrollView.addSubview(hStack) scrollView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(scrollView) let g = view.safeAreaLayoutGuide let cg = scrollView.contentLayoutGuide let fg = scrollView.frameLayoutGuide NSLayoutConstraint.activate([ scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0), scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0), scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0), scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0), hStack.topAnchor.constraint(equalTo: cg.topAnchor, constant: 8.0), hStack.leadingAnchor.constraint(equalTo: cg.leadingAnchor, constant: 8.0), hStack.trailingAnchor.constraint(equalTo: cg.trailingAnchor, constant: -8.0), hStack.bottomAnchor.constraint(equalTo: cg.bottomAnchor, constant: -8.0), hStack.widthAnchor.constraint(equalTo: fg.widthAnchor, constant: -16.0), ]) let infoFont: UIFont = .italicSystemFont(ofSize: 10.0) let testFont: UIFont = .systemFont(ofSize: 12.0) var testStr: String = "This is some example text to test truncation consistency. At the end of this line, is a pair of newline chars:\n\nA HEADLINE\n\nThe headline was also followed by a pair of newline chars." for i in 3...10 { let infoLabel = UILabel() infoLabel.font = infoFont infoLabel.text = "UILabel - numLines: \(i)" labelStackView.addArrangedSubview(infoLabel) let v = UILabel() v.font = testFont v.numberOfLines = i v.text = testStr v.backgroundColor = .yellow v.lineBreakMode = .byTruncatingTail labelStackView.addArrangedSubview(v) labelStackView.setCustomSpacing(16.0, after: v) } for i in 3...10 { let infoLabel = UILabel() infoLabel.font = infoFont infoLabel.text = "TextViewLabel - numLines: \(i)" textViewStackView.addArrangedSubview(infoLabel) let v = TextViewLabel() v.font = testFont v.numberOfLines = i v.text = testStr v.backgroundColor = .cyan v.lineBreakMode = .byTruncatingTail textViewStackView.addArrangedSubview(v) textViewStackView.setCustomSpacing(16.0, after: v) } scrollView.backgroundColor = UIColor(white: 0.95, alpha: 1.0) } }