Skip to content

Instantly share code, notes, and snippets.

@shawn-frank
Last active March 7, 2022 03:23
Show Gist options
  • Save shawn-frank/3654138963df191bf5b5a780b6249362 to your computer and use it in GitHub Desktop.
Save shawn-frank/3654138963df191bf5b5a780b6249362 to your computer and use it in GitHub Desktop.
This is a playground example of how to underline text that occurs a specified number of times in response to this stack overflow question: https://stackoverflow.com/q/71359204/1619193
import UIKit
struct MatchProperty
{
// Stores the index of the iCor array the current
// word belongs to
var iCorRIndex: Int
// Stores the index of the sentence in the which the word
// was encountered in the current iCor array
// Here I refer to each item in an iCor array as a sentence
var sentenceIndex: Int
// Stores the range of the word in the string
var range: NSRange
}
// Original data
let iCor1R = ["Lest any should say that I had baptized in mine own name.",
"Grace be unto you, and peace, from God our Father, and from the.",
"Even as the testimony of Christ was confirmed in you:"]
let iCor2R = ["And‭ I‭ was‭‭ with‭ you‭ in‭ weakness‭, and‭ in‭ fear‭, and‭ in‭ much‭ trembling‭.",
"But‭ he that is spiritual‭ judgeth‭‭‭ all things‭, yet‭ God himself‭ is judged‭‭ of‭ no man‭.",]
let iCor3R = ["‭I‭ have planted‭‭, Apollos‭ watered‭‭; but‭ God gave the increase‭‭.",
"‭Therefore‭ let‭‭ no man‭ glory‭‭ in‭ men‭. For‭ all things‭ are‭‭ yours‭;",
"‭And‭ ye‭ are Christ’s‭; and‭ Christ‭ [is] God’s‭."]
var iCorR: [[String]] = []
var ICorTEXT: [[NSMutableAttributedString]] = []
let label = UILabel()
// Hash to store words and where they occurred
// This assumes you want to count the occurrences in all your string arrays
// If you want to count it for each string array, initialize this inside the
// loop
var wordOccurrences: [String: [MatchProperty]] = [:]
/// Stores the array index and position a word belongs to
private func storeWordPositions()
{
iCorR = [
iCor1R,
iCor2R,
iCor3R
]
for (iCorRIndex, strings) in iCorR.enumerated()
{
// Loop through the string in the strings array
for (sentenceIndex, 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(iCorRIndex: iCorRIndex,
sentenceIndex: sentenceIndex,
range: NSRange(substringRange,
in: string))
// Store the match
matchesInfo.append(matchProperty)
wordOccurrences[word] = matchesInfo
}
}
}
}
}
/// Underlines the words that occur a specified number of times
private func underlineWords(thatOccur occur: Int)
{
// This will be used to store the updated versions of the strings
// with the underlines
var updatedStrings
= iCorR.map{ $0.map { NSMutableAttributedString(string: $0) } }
// Optimize by filtering only the words that need processing
let occurrences = wordOccurrences.filter { return $1.count == occur }
// Loop through the occurrences of words
for occurrence in occurrences.values
{
// Check which words occurred twice
if occurrence.count == occur
{
// Iterate over the occurrences
for matchProperty in occurrence
{
// Retrieve the attributed version of the string
let attributedString
= updatedStrings[matchProperty.iCorRIndex][matchProperty.sentenceIndex]
// Underline words that have occurred twice
attributedString.addAttribute(.underlineStyle,
value: NSUnderlineStyle.double.rawValue,
range: matchProperty.range)
// Add yellow background as well (optional, just to see easily)
attributedString.addAttribute(.backgroundColor,
value: UIColor.yellow,
range: matchProperty.range)
// Update the attributed string data
updatedStrings[matchProperty.iCorRIndex][matchProperty.sentenceIndex]
= attributedString
}
}
}
ICorTEXT = updatedStrings
}
/// Update the UI
/// Not used in regular playground
private func updateLabel()
{
// 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 = ICorTEXT.flatMap{ $0 }.joined(separator: "\n")
// Set the attributed text where you want
// Cannot see anything in playground
label.attributedText = attributedString
}
extension Array where Element: NSMutableAttributedString
{
/// Returns a new attributed string by concatenating the elements of the sequence, adding the given separator between each element.
/// - parameters:
/// - separator: A string to insert between each of the elements in this sequence. The default separator is an empty string.
func joined(separator: NSMutableAttributedString = NSMutableAttributedString(string: "")) -> NSMutableAttributedString
{
var isFirst = true
return self.reduce(NSMutableAttributedString())
{
(r, e) in
if isFirst
{
isFirst = false
}
else
{
r.append(separator)
}
r.append(e)
return r
}
}
/// Returns a new attributed string by concatenating the elements of the sequence, adding the given separator between each element.
/// - parameters:
/// - separator: A string to insert between each of the elements in this sequence. The default separator is an empty string.
func joined(separator: String = "") -> NSMutableAttributedString
{
return joined(separator: NSMutableAttributedString(string: separator))
}
}
label.frame = CGRect(x: 0, y: 0, width: 375, height: 500)
storeWordPositions()
underlineWords(thatOccur: 3)
// Nothing to see with label, use iOS App or Single View Playground to see this
// updateLabel()
print(ICorTEXT)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment