I have found that time of the string colouring depends on how many different NSColors are used. In code below if I use only one colour for the three cases then the text colouring process is 3 times faster than in the case when three different colours are used for these three cases, each colour for each case. Why ? Is there a way not to slow down the colouring for three different colours ?
for i in 0..<arrayOfNSRangesForA.count
{
textFromStorage.addAttribute(NSForegroundColorAttributeName, value: NSColor.green, range: arrayOfNSRangesForA[i])
}
for i in 0..<arrayOfNSRangesForT.count
{
textFromStorage.addAttribute(NSForegroundColorAttributeName, value: NSColor.green, range: arrayOfNSRangesForT[i])
}
for i in 0..<arrayOfNSRangesForC.count
{
textFromStorage.addAttribute(NSForegroundColorAttributeName, value: NSColor.green, range: arrayOfNSRangesForC[i])
}
Update
I have found one more BAD thing. When I changed colouring from NSForegroundColorAttributeName
to NSBackgroundColorAttributeName
the running time has increased significantly - 10 times. For 20 000 characters, it was for one colour, for NSForegroundColorAttributeName
- 1 sec, for NSBackgroundColorAttributeName
- 10 sec; if three colours - 3 and 30 sec accordingly. For me it is very bad feature of Swift !!! It is not possible to do normal work with DNA (ATGC sequence) colouring, since the length of DNA is thousands of A,T,G,C characters!
Update In comments I have a suggestion to colour only visible part of text. I have tried this approach and it is much worse even for shorter text in comparison with what I did in standard way. So, I had NSRange of text for visible part of text, and did colouring on fly while scrolling by using notification when scrolling is on. It is a bad way.
The biggest obstacle is laying out all these attributed characters in the text view. Colorize the DNA sequence takes minimal amount of time. Instead of writing your own layout manager or text storage class, you can adopt a divide-and-conquer approach by colorizing the text view in chunks at a time:
@IBOutlet var textView: NSTextView!
var dnaSequence: String!
var attributedDNASequence: NSAttributedString!
@IBAction func colorize(_ sender: Any) {
self.dnaSequence = "ACGT" // your plaintext DNA sequence
self.attributedDNASequence = self.makeAttributedDNASequence()
// Rendering long string with the same attributes throughout is extremely fast
self.textView.textStorage?.setAttributedString(NSAttributedString(string: dnaSequence))
let step = 10_000 // colorize 10k characters at a time
let delay = 0.2 // delay between each render
for (i, location) in stride(from: 0, to: self.dnaSequence.characters.count, by: step).enumerated() {
let length = min(step, self.dnaSequence.characters.count - location)
let range = NSMakeRange(location, length)
// Since we are modifying the textStorage of a GUI object (NSTextView)
// we should do it on the main thread
DispatchQueue.main.asyncAfter(deadline: .now() + (delay * Double(i))) {
let subtext = self.attributedDNASequence.attributedSubstring(from: range)
print("Replacing text in range \(location) to \(location + length)")
self.textView.textStorage?.replaceCharacters(in: range, with: subtext)
}
}
}
// MARK: -
var colorA = NSColor.red
var colorC = NSColor.green
var colorG = NSColor.blue
var colorT = NSColor.black
func makeAttributedDNASequence() -> NSAttributedString {
let attributedText = NSMutableAttributedString(string: dnaSequence)
var index = dnaSequence.startIndex
var color: NSColor!
for i in 0..<dnaSequence.characters.count {
switch dnaSequence[index] {
case "A":
color = colorA
case "C":
color = colorC
case "G":
color = colorG
case "T":
color = colorT
default:
color = NSColor.black
}
attributedText.addAttribute(NSForegroundColorAttributeName, value: color, range: NSMakeRange(i,1))
index = dnaSequence.index(after: index)
}
return attributedText
}
The trick is to make the application as responsive as possible so the user is unaware that things are still being done in the background. With a small delay
(<= 0.3 second) I couldn't scroll my mouse fast enough to reach the end of text view before everything has been colorized (100k characters).
On a 100k-character test, it took 0.7 seconds to until the colorized string first appeared inside the text view instead of the 7 seconds if I did everything at once.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With