I have an array of strings. I need a way to find all keywords (once or twice used) and underline them. At the moment, I'm converting them to NSMutableAttributedString
which is working, but that just displays the text. Is there a way to write a for loop or something that would find all the ranges of keywords and apply underline attribute to them? Am I doing it all wrong? I'm teaching myself Swift by writing an app instead of taking a course... probably not very smart. Thank you for your help!
I understand that having the client prosses the strings for each keyword each time it is run is very ineffieciet. The strings need to be easily updated, so (for now) it's faster to prosses each time.
Update
Based on Jon's (OP) comments:
How do I split it back into the original array? I need the original array with the added underlines.
In order to that, we need to keep track of the word count as before using the hash table. However, the range will not be enough as we needed the added information of which string in the original array the word belongs to.
In order to do that, I created a struct MatchProperty
to store this information:
struct MatchProperty
{
// Stores the index of which array the word
// was encountered
var index: Int
// Stores the range of the word in the string
var range: NSRange
}
Then I made some small updates to the logic
// Array of strings
let strings = ["This will occur twice",
"Will also occurs two times",
"But where is something else",
"Other things happen once this time"]
// Hash to store words and where they occurred
var wordOccurrences: [String: [MatchProperty]] = [:]
// Loop through the string in the strings array
for (index, string) in strings.enumerated()
{
// Loop through all the words in each string of the string array
string.enumerateSubstrings(in: string.startIndex...,
options: .byWords)
{ word, substringRange, _, _ in
// Lower case the word
if let word = word?.lowercased()
{
// Check if we have come across the word before or not
// If we have, retrieve the match property for the word
// If not, we will initialize a new array to store the match
// properties of the first occurrence
var matchesInfo = wordOccurrences[word, default: []]
// Create a match location object with the index of where
// the word occurs in the strings array along with its range
let matchProperty = MatchProperty(index: index,
range: NSRange(substringRange,
in: string))
// Store the match
matchesInfo.append(matchProperty)
wordOccurrences[word] = matchesInfo
}
}
}
// This will be used to store the updated versions of the strings
// with the underlines
var updatedStrings
= strings.map { return NSMutableAttributedString(string: $0) }
// Loop through the occurrences of words
for occurrence in wordOccurrences.values
{
// Check which words occurred twice
if occurrence.count == 2
{
// Iterate over the occurrences
for matchProperty in occurrence
{
// Retrieve the attributed version of the string
let attributedString = updatedStrings[matchProperty.index]
// Underline words that have occurred twice
attributedString.addAttribute(.underlineStyle,
value: NSUnderlineStyle.double.rawValue,
range: matchProperty.range)
// Update the attributed string data
updatedStrings[matchProperty.index] = attributedString
}
}
}
// I am joining all the updated versions of the original string
// This is not needed, I am just using it to display the original
// strings but with their updates
// REF: https://stackoverflow.com/a/48583402/1619193
let attributedString = updatedStrings.joined(separator: "\n")
// Set the attributed text where you want
label.attributedText = attributedString
The function joined
is not something that comes by default for arrays of NSMutableAttributedStrings
, so I used