I've been struggling few days how to implement properly the same "read more" behavior like in the iOS App Store (screenshot attached).
I am using AutoLayout
in my project. The UITableView
rows are using the self sizing height of rows by: tableView.rowHeight = UITableView.automaticDimension
.
I have tried many ways, going through UITextView
attributed strings, truncating it manually and re-issuing layout requests. My last attempt is to try using the textView.textContainer.exclusionPaths
. Here's the code that I have got till now:
import UIKit
class ReadMoreTextViewCell: UITableViewCell {
static var mediumFont: UIFont {
return UIFont.systemFont(ofSize: 16)
}
private let textView = UITextView()
private let button: UIButton = {
let button = UIButton(type: .custom)
return button
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
setupConstraints()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupViews()
setupConstraints()
}
private func setupViews() {
textView.isScrollEnabled = false
textView.textContainer.lineFragmentPadding = 0.0
textView.textContainerInset = .zero
textView.backgroundColor = UIColor.clear
textView.textColor = .black
textView.textContainer.maximumNumberOfLines = 3
textView.textContainer.lineBreakMode = .byTruncatingTail
contentView.addSubview(textView)
button.setTitle("Read more", for: .normal)
button.titleLabel?.font = ReadMoreTextViewCell.mediumFont
button.setTitleColor(UIColor.blue, for: .normal)
button.addTarget(self, action: #selector(readMoreTapped), for: .touchUpInside)
button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: -6, right: 0)
contentView.addSubview(button)
}
private func setupConstraints() {
textView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
textView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
textView.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
textView.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor),
textView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor)
])
}
override func layoutSubviews() {
super.layoutSubviews()
contentView.layoutIfNeeded()
let buttonFrame = CGRect(x: textView.bounds.width - 80, y: textView.bounds.height - 26, width: 80, height: 26)
button.frame = buttonFrame
let buttonPath = UIBezierPath(rect: button.frame)
textView.textContainer.exclusionPaths = [buttonPath]
}
override func prepareForReuse() {
super.prepareForReuse()
textView.delegate = .none
textView.attributedText = .none
}
func setup(with text: String) {
textView.attributedText = text.attributedString(font: UIFont.systemFont(ofSize: 16))
}
@objc func readMoreTapped() {
}
}
The result is like in the screenshot below:
after refreshing the cells by pulling the out of visible UITableView
area:
Final remarks, I need to use the UITextView
here.
If you have resolved this in some of your projects, please help :-)