Last active
March 7, 2022 03:23
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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