0

I've been struggling few days how to implement properly the same "read more" behavior like in the iOS App Store (screenshot attached).

enter image description here

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:

enter image description here

after refreshing the cells by pulling the out of visible UITableView area: enter image description here

Final remarks, I need to use the UITextView here. If you have resolved this in some of your projects, please help :-)

piotr_ch
  • 508
  • 3
  • 7
  • 17
  • Maybe https://stackoverflow.com/questions/32309247/add-read-more-to-the-end-of-uilabel will help. – rmaddy Oct 28 '19 at 18:51
  • @rmaddy thank you, but I have seen it already and it's not my case / not working for me, since I am inside the `UITableViewCell`. – piotr_ch Oct 28 '19 at 19:16
  • How much should be the height in case when you show read more and the text is truncated? – DionizB Oct 28 '19 at 21:15

1 Answers1

1

If you're just trying to get the correct placement of the "Read More", you can do something like below.
But this does not take into account the following:

  1. showing or hiding the "Read more" button based on length of textView text, lets say there are only 2 lines
  2. Action to expand the tableview cell on button click.

In the setupViews I removed: button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: -6, right: 0) and added button.backgroundColor = .white

Back to the code, update how you're setting the constraints on the button. Instead of setting them with frame, do the following.

    button.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        button.lastBaselineAnchor.constraint(equalTo: textView.lastBaselineAnchor),
        button.heightAnchor.constraint(equalToConstant: 20),
        button.widthAnchor.constraint(equalToConstant: 80),
        button.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor)
    ])

With the above, I got this as the result enter image description here

The above can still be tweaked a little, but thats a quick fix to get you on the right path, assuming I understood your question.

valosip
  • 3,167
  • 1
  • 14
  • 26