Reputation: 154
I am trying to split paragraphs into an array of components using the code I have typed below. I am having trouble with the Ranges element though. My problem is that if the user types a string multiple times it returns the first times range and not the one I am wanting.
Example:
Paragraph 1: Banana
Paragraph 2: Apple
Paragraph 3: Banana
Both Paragraph 1 and 3 will have Paragraph 1's range. What is a smarter way of going about this to fix this problem?
lazy var models: [ParagraphModel] = []
struct ParagraphModel {
let text: String
let range: Range<String.Index>
}
func getParagraphs(){
let components = textView.text.components(separatedBy: "\n")
let models = components.compactMap { component -> ParagraphModel? in
if let range = textView.text.range(of: component) {
return ParagraphModel(text: component, range: range)
}
return nil
}
self.models = models
}
Updated Code from @Rob:
func getParagraphModel() {
guard let text = noteContents.text else { return }
var currentModels: [ParagraphModel] = []
text.enumerateSubstrings(in: text.startIndex..., options: .byParagraphs) { substring, range, _, stop in
if let substring = substring, !substring.isEmpty,
let textRange = self.noteContents.text.range(of: substring)
{
currentModels.append(ParagraphModel(text: substring, range: textRange))
}
}
self.models = currentModels
}
When typing the example Text the printout to the console is:
[Project.ViewController.ParagraphModel(text: "Banana", range: Range(Swift.String.Index(_rawBits: 0)..<Swift.String.Index(_rawBits: 393216))), Project.ViewController.ParagraphModel(text: "Apple", range: Range(Swift.String.Index(_rawBits: 458752)..<Swift.String.Index(_rawBits: 786432))), Project.ViewController.ParagraphModel(text: "Banana", range: Range(Swift.String.Index(_rawBits: 0)..<Swift.String.Index(_rawBits: 393216)))]
Upvotes: 1
Views: 641
Reputation: 437432
The problem in your code snippet is that you’re always going to find the first occurrence of the target string. So, when it’s searching for the third paragraph, “Banana”, it’s going to find the first occurrence of that string.
I would suggest text.enumerateSubstrings(in:options:_:)
to get the ranges of the paragraphs within text
. This will enumerate the substrings and their respective ranges, which avoids you searching for the text yourself, avoiding problems that can arise if the same string appears multiple times.
Thus:
func getParagraphs() {
guard let textView = textView, let text = textView.text else { return }
models = []
text.enumerateSubstrings(in: text.startIndex..., options: .byParagraphs) { substring, range, _, _ in
guard let substring = substring, !substring.isEmpty else {
return
}
self.models.append(ParagraphModel(text: substring, range: range))
}
}
That produces:
[
MyApp.ParagraphModel(text: "Paragraph 1: Banana", range: Range(Swift.String.Index(_rawBits: 0)..<Swift.String.Index(_rawBits: 1245184))),
MyApp.ParagraphModel(text: "Paragraph 2: Apple", range: Range(Swift.String.Index(_rawBits: 1376256)..<Swift.String.Index(_rawBits: 2555904))),
MyApp.ParagraphModel(text: "Paragraph 3: Banana", range: Range(Swift.String.Index(_rawBits: 2686976)..<Swift.String.Index(_rawBits: 3932160)))
]
Or, if range
in ParagraphModel
was a UITextRange
, you’d do:
func getParagraphs() {
guard let textView = textView, let text = textView.text else { return }
models = []
text.enumerateSubstrings(in: text.startIndex..., options: .byParagraphs) { substring, range, _, stop in
let nsRange = NSRange(range, in: text)
if let substring = substring,
!substring.isEmpty,
let start = textView.position(from: textView.beginningOfDocument, offset: nsRange.location),
let end = textView.position(from: start, offset: nsRange.length),
let textRange = self.textView.textRange(from: start, to: end)
{
self.models.append(ParagraphModel(text: substring, range: textRange))
}
}
}
Producing:
[
MyApp.ParagraphModel(text: "Paragraph 1: Banana", range: <_UITextKitTextRange: 0x6000037983e0> (0, 19)F),
MyApp.ParagraphModel(text: "Paragraph 2: Apple", range: <_UITextKitTextRange: 0x600003798260> (21, 18)F),
MyApp.ParagraphModel(text: "Paragraph 3: Banana", range: <_UITextKitTextRange: 0x600003799f20> (41, 19)F)
]
Upvotes: 3