Adding Background Color with rounded corners in UITextView's Text. This answer will give some ideas for your Question.
Logic:
In UITextView, I have added UITapGestureRecognizer, which detects user's tap action Character by Character. If user, taps on any one of Character in subString, new UIView will be created and triggering Timer. When timer gets end, created UIView will be removed from UITextView.
With the help of, myTextView.position, we can get subString's CGRect. That is frame for Created UIView. Size (WIDTH) for each words in subString, can get from SizeAtrributes.
@IBOutlet weak var challengeTextVw: UITextView!
let fullText = "We Love Swift and Swift attributed text "
var myString = NSMutableAttributedString ()
let subString = " Swift attributed text "
var subStringSizesArr = [CGFloat]()
var myRange = NSRange()
var myWholeRange = NSRange()
let fontSize : CGFloat = 25
var timerTxt = Timer()
let delay = 3.0
override func viewDidLoad() {
super.viewDidLoad()
myString = NSMutableAttributedString(string: fullText)
myRange = (fullText as! NSString).range(of: subString)
myWholeRange = (fullText as! NSString).range(of: fullText)
let substringSeperatorArr = subString.components(separatedBy: " ")
print(substringSeperatorArr)
print(substringSeperatorArr.count)
var strConcat = " "
for str in 0..<substringSeperatorArr.count
{
strConcat = strConcat + substringSeperatorArr[str] + " "
let textSize = (strConcat as! NSString).size(withAttributes: [NSAttributedStringKey.font : UIFont.systemFont(ofSize: fontSize)])
print("strConcatstrConcat ", strConcat)
if str != 0 && str != (substringSeperatorArr.count - 2)
{
print("times")
subStringSizesArr.append(textSize.width)
}
}
let myCustomAttribute = [NSAttributedStringKey.init("MyCustomAttributeName") : "some value", NSAttributedStringKey.foregroundColor : UIColor.orange] as [NSAttributedStringKey : Any]
let fontAtrib = [NSAttributedStringKey.font : UIFont.systemFont(ofSize: fontSize)]
myString.addAttributes(myCustomAttribute, range: myRange)
myString.addAttributes(fontAtrib, range: myWholeRange)
challengeTextVw.attributedText = myString
let tap = UITapGestureRecognizer(target: self, action: #selector(myMethodToHandleTap))
tap.delegate = self
challengeTextVw.addGestureRecognizer(tap)
challengeTextVw.isEditable = false
challengeTextVw.isSelectable = false
}
@objc func myMethodToHandleTap(_ sender: UITapGestureRecognizer) {
let myTextView = sender.view as! UITextView
let layoutManager = myTextView.layoutManager
let numberOfGlyphs = layoutManager.numberOfGlyphs
var numberOfLines = 0
var index = 0
var lineRange:NSRange = NSRange()
while (index < numberOfGlyphs) {
layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
index = NSMaxRange(lineRange);
numberOfLines = numberOfLines + 1
}
print("noLin ", numberOfLines)
// location of tap in myTextView coordinates and taking the inset into account
var location = sender.location(in: myTextView)
location.x -= myTextView.textContainerInset.left;
location.y -= myTextView.textContainerInset.top;
// character index at tap location
let characterIndex = layoutManager.characterIndex(for: location, in: myTextView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
// if index is valid then do something.
if characterIndex < myTextView.textStorage.length
{
// print the character index
print("character index: \(characterIndex)")
// print the character at the index
let myRangee = NSRange(location: characterIndex, length: 1)
let substring = (myTextView.attributedText.string as NSString).substring(with: myRangee)
print("character at index: \(substring)")
// check if the tap location has a certain attribute
let attributeName = NSAttributedStringKey.init("MyCustomAttributeName")
let attributeValue = myTextView.attributedText.attribute(attributeName, at: characterIndex, effectiveRange: nil) as? String
if let value = attributeValue
{
print("You tapped on \(attributeName) and the value is: \(value)")
print("\n\n ererereerer")
timerTxt = Timer.scheduledTimer(timeInterval: delay, target: self, selector: #selector(delayedAction), userInfo: nil, repeats: false)
myTextView.layoutManager.ensureLayout(for: myTextView.textContainer)
// text position of the range.location
let start = myTextView.position(from: myTextView.beginningOfDocument, offset: myRange.location)!
// text position of the end of the range
let end = myTextView.position(from: start, offset: myRange.length)!
// text range of the range
let tRange = myTextView.textRange(from: start, to: end)
// here it is!
let rect = myTextView.firstRect(for: tRange!) //firstRectForRange(tRange)
var secondViewWidthIndex = Int()
for count in 0..<subStringSizesArr.count
{
if rect.width > subStringSizesArr[count]
{
secondViewWidthIndex = count
}
}
let backHideVw = UIView()
backHideVw.frame.origin.x = rect.origin.x
backHideVw.frame.origin.y = rect.origin.y + 1
backHideVw.frame.size.height = rect.height
backHideVw.frame.size.width = rect.width
backHideVw.backgroundColor = UIColor.brown
backHideVw.layer.cornerRadius = 2
backHideVw.tag = 10
myTextView.addSubview(backHideVw)
myTextView.sendSubview(toBack: backHideVw)
if numberOfLines > 1
{
let secondView = UIView()
secondView.frame.origin.x = 0
secondView.frame.origin.y = backHideVw.frame.origin.y + backHideVw.frame.size.height
secondView.frame.size.height = backHideVw.frame.size.height
secondView.frame.size.width = (subStringSizesArr.last! - subStringSizesArr[secondViewWidthIndex]) + 2
secondView.backgroundColor = UIColor.brown
secondView.layer.cornerRadius = 2
secondView.tag = 20
backHideVw.frame.size.width = subStringSizesArr[secondViewWidthIndex]
myTextView.addSubview(secondView)
print("secondView.framesecondView.frame ", secondView.frame)
myTextView.sendSubview(toBack: secondView)
}
print("rectrect ", rect)
}
}
}
@objc func delayedAction()
{
for subVws in challengeTextVw.subviews
{
if (String(describing: subVws).range(of:"UIView") != nil)
{
if (subVws as! UIView).tag == 10 || (subVws as! UIView).tag == 20
{
subVws.removeFromSuperview()
}
}
}
}
All attempts tried, by increasing Font Size.
Attempt 1

Attempt 2

Attempt 3
