Reputation: 55
I am trying to highlight line by line the text in a UITextView
. I want to iterate over each line and highlight that one for the user to see, then I want to remove the highlighting effect in preparation for the next line. I have tried and failed to create a solution and this is my best chance right now.
Here is some of what I have been working on so far, it currently fills the UITextView
with "NSBackgroundColor 1101
" for some reason and I do not know why that is.
func highlight() {
let str = "This is\n some placeholder\n text\nwith newlines."
var newStr = NSMutableAttributedString(string: "")
var arr:[String] = str.components(separatedBy: "\n")
var attArr:[NSMutableAttributedString] = []
for i in 0..<arr.count {
attArr.append(NSMutableAttributedString(string: arr[i]))
}
for j in 0..<attArr.count {
let range = NSMakeRange(0, attArr[j].length)
attArr[j].addAttribute(.backgroundColor, value: UIColor.yellow, range: range)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5){
for m in 0..<attArr.count {
newStr = NSMutableAttributedString(string: "\(attArr[m])\n")
self.textView.attributedText = newStr
}
}
attArr[j].removeAttribute(.backgroundColor, range: range)
//remove from texview here
}
}
As you can see this algorithm is supposed to strip the textView
text and place that into an array by separating each line the new line delimiter.
The next thing done is to create an array filled with the same text but as mutable attributed string to begin adding the highlight attribute.
Each time a highlighted line appears there is a small delay until the next line begins to highlight. If anyone can help or point me in to the right direction to begin implementing this correctly it would help immensely,
Thank you!
Upvotes: 3
Views: 3853
Reputation: 386018
So you want this:
You need the text view's contents to always be the full string, with one line highlighted, but your code sets it to just the highlighted line. Your code also schedules all the highlights to happen at the same time (.now() + 0.5
) instead of at different times.
Here's what I'd suggest:
Create an array of ranges, one range per line.
Use that array to modify the text view's textStorage
by removing and adding the .backgroundColor
attribute as needed to highlight and unhighlight lines.
When you highlight line n
, schedule the highlighting of line n+1
. This has two advantages: it will be easier and more efficient to cancel the animation early if you need to, and it will be easier to make the animation repeat endlessly if you need to.
I created the demo above using this playground:
import UIKit
import PlaygroundSupport
let text = "This is\n some placeholder\n text\nwith newlines."
let textView = UITextView(frame: CGRect(x: 0, y:0, width: 200, height: 100))
textView.backgroundColor = .white
textView.text = text
let textStorage = textView.textStorage
// Use NSString here because textStorage expects the kind of ranges returned by NSString,
// not the kind of ranges returned by String.
let storageString = textStorage.string as NSString
var lineRanges = [NSRange]()
storageString.enumerateSubstrings(in: NSMakeRange(0, storageString.length), options: .byLines, using: { (_, lineRange, _, _) in
lineRanges.append(lineRange)
})
func setBackgroundColor(_ color: UIColor?, forLine line: Int) {
if let color = color {
textStorage.addAttribute(.backgroundColor, value: color, range: lineRanges[line])
} else {
textStorage.removeAttribute(.backgroundColor, range: lineRanges[line])
}
}
func scheduleHighlighting(ofLine line: Int) {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
if line > 0 { setBackgroundColor(nil, forLine: line - 1) }
guard line < lineRanges.count else { return }
setBackgroundColor(.yellow, forLine: line)
scheduleHighlighting(ofLine: line + 1)
}
}
scheduleHighlighting(ofLine: 0)
PlaygroundPage.current.liveView = textView
Upvotes: 1