8

My Swift app involves searching through text in a UITextView. The user can search for a certain substring within that text view, then jump to any instance of that string in the text view (say, the third instance). I need to find out the integer value of which character they are on.

For example:

Example 1: The user searches for "hello" and the text view reads "hey hi hello, hey hi hello", then the user presses down arrow to view second instance. I need to know the integer value of the first h in the second hello (i.e. which # character that h in hello is within the text view). The integer value should be 22.

Example 2: The user searches for "abc" while the text view reads "abcd" and they are looking for the first instance of abc, so the integer value should be 1 (which is the integer value of that a since it's the first character of the instance they're searching for).

How can I get the index of the character the user is searching for?

4
  • 1
    This is mostly a duplicate of stackoverflow.com/questions/28685420/… Commented Sep 6, 2016 at 22:45
  • @rmaddy Seems helpful to me, but not quite exactly what I'm asking here. Thank you though. Commented Sep 6, 2016 at 23:01
  • 2
    The answers are exactly what you need in general. You get the 2nd substring by passing a range that starts at the end of the 1st substring. Commented Sep 6, 2016 at 23:03
  • Possible duplicate of Search string for repeating instances of a word Commented Feb 2, 2018 at 1:24

2 Answers 2

12

Xcode 11 • Swift 5 or later

let sentence = "hey hi hello, hey hi hello"
let query = "hello"
var searchRange = sentence.startIndex..<sentence.endIndex
var indices: [String.Index] = []

while let range = sentence.range(of: query, options: .caseInsensitive, range: searchRange) {
    searchRange = range.upperBound..<searchRange.upperBound
    indices.append(range.lowerBound)
}

print(indices)   // "[7, 21]\n"
Sign up to request clarification or add additional context in comments.

2 Comments

Output will be like [Swift.String.Index(_rawBits: 1), Swift.String.Index(_rawBits: 524288)]
Yes it changed somewhere in the Swift evolution process but it is correct.
5

Another approach is NSRegularExpression which is designed to easily iterate through matches in an string. And if you use the .ignoreMetacharacters option, it will not apply any sophisticated wildcard/regex logic, but will just look for the string in question. So consider:

let string = "hey hi hello, hey hi hello"  // string to search within
let searchString = "hello"                 // string to search for
let matchToFind = 2                        // grab the second occurrence

let regex = try! NSRegularExpression(pattern: searchString, options: [.caseInsensitive, .ignoreMetacharacters])

You could use enumerateMatches:

var count = 0
let range = NSRange(string.startIndex ..< string.endIndex, in: string)
regex.enumerateMatches(in: string, range: range) { result, _, stop in
    count += 1
    if count == matchToFind {
        print(result!.range.location)
        stop.pointee = true
    }
}

Or you can just find all of them with matches(in:range:) and then grab the n'th one:

let matches = regex.matches(in: string, range: range)
if matches.count >= matchToFind {
    print(matches[matchToFind - 1].range.location)
}

Obviously, if you were so inclined, you could omit the .ignoreMetacharacters option and allow the user to perform regex searches, too (e.g. wildcards, whole word searches, start of word, etc.).

For Swift 2, see previous revision of this answer.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.