1

I need to add "... Read more" to the end of UITextView. I use this solution Add "...Read More" to the end of UILabel. I tried to use it with UILabel - it works perfectly. But in UITextView "... Read more" position isn't stable. I think the problem is in textViewWidth and textViewHeight. When I change their values to -10 it works better but not really. How can I solve it? My code:

    var visibleTextLength: Int {
        guard let text = text else { return 0 }
        
        let mode: NSLineBreakMode = textContainer.lineBreakMode
        let textViewWidth: CGFloat = frame.size.width //adding - 10 helps a little
        let textViewHeight: CGFloat = frame.size.height //adding - 10 helps a little
        let sizeConstraint = CGSize(width: textViewWidth, height: CGFloat.greatestFiniteMagnitude)
        
        let attributes: [AnyHashable: Any] = [NSAttributedString.Key.font: font!]
        let attributedText = NSAttributedString(string: text, attributes: attributes as? [NSAttributedString.Key: Any])
        let boundingRect: CGRect = attributedText.boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, context: nil)
        
        if boundingRect.size.height > textViewHeight {
            var index = 0
            var prev = 0
            let characterSet = CharacterSet.alphanumerics
            repeat {
                prev = index
                if mode == NSLineBreakMode.byCharWrapping {
                    index += 1
                } else {
                    index = (text as NSString).rangeOfCharacter(from: characterSet, options: [], range: NSRange(location: index + 1, length: text.utf16.count - index - 1)).location
                }
            } while index != NSNotFound && index < text.utf16.count && (text as NSString).substring(to: index).boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, attributes: attributes as? [NSAttributedString.Key: Any], context: nil).size.height <= textViewHeight
            return prev
        }
        return text.utf16.count
    }
    
    func addTrailing(with trailingText: String, moreText: String, moreTextFont: UIFont?) {
        guard self.visibleTextLength > 0 else { return }
        
        let readMoreText: String = trailingText + moreText
        let lengthForVisibleString: Int = self.visibleTextLength
        
        if let text = self.text {
            let mutableString: String = text
            let trimmedString: String? = (mutableString as NSString).replacingCharacters(in: NSRange(location: lengthForVisibleString, length: text.utf16.count - lengthForVisibleString), with: "")
            let readMoreLength: Int = (readMoreText.utf16.count)
            
            guard let safeTrimmedString = trimmedString, safeTrimmedString.utf16.count >= readMoreLength else { return }
            
            let trimmedForReadMore: String = (safeTrimmedString as NSString).replacingCharacters(in: NSRange(location: safeTrimmedString.utf16.count - readMoreLength, length: readMoreLength), with: "") + trailingText
            
            let attributedMoreText = NSMutableAttributedString(string: trimmedForReadMore,
                                                               attributes: [NSAttributedString.Key.font: font!])
            
            if let moreTextFont = moreTextFont {
                let readMoreAttributed = NSMutableAttributedString(string: moreText, attributes: [NSAttributedString.Key.font: moreTextFont])
                attributedMoreText.append(readMoreAttributed)
            }
            
            self.attributedText = attributedMoreText
        }
    }

enter image description here

enter image description here

With - 10 in textViewWidth and textViewHeight

enter image description here

enter image description here

  • I guess that the issue is because you use `frame` while you should also take in account the `textContainerInset` https://developer.apple.com/documentation/uikit/uitextview/1618619-textcontainerinset?language Also, you seem to use only `.usesLineFragmentOrigin`, maybe add `.usesFontLeading` too: `options: [. usesLineFragmentOrigin, . usesFontLeading]`? – Larme Jun 25 '21 at 07:21
  • Should I subtract textContainerInset values from frame.size.width and frame.size.height ? Or how should I use it? – VyacheslavBakinkskiy Jun 25 '21 at 08:23
  • I would substract it: `let textViewWidth: CGFloat = frame.size.width - (textContainerInset.left + textContainerInset.right)` ? – Larme Jun 25 '21 at 08:41
  • Unfortunately it didn't help. The best solution what I found is: `let textViewWidth: CGFloat = frame.size.width - textContainer.lineFragmentPadding * 2 let textViewHeight: CGFloat = frame.size.height - textContainerInset.top - textContainerInset.bottom` But "Read more" text still has different position – VyacheslavBakinkskiy Jun 25 '21 at 08:59

0 Answers0