Search code examples
swiftmacosnstextviewnsscrollview

Find a substring within a NSTextView and then scroll to the substring's location


I am building a simple Mac App that has a NSTextView with very long string (15mb worth of text). I want to find a specific substring (a timestamp) and then scroll down to the location of the substring in the NSTextView.

Right now I am able to find the index of where the substring is but it is weirdly formatted. I used the top answer here: Index of a substring in a string with Swift

@IBOutlet weak var logTextView: NSScrollView!
@IBOutlet var logTextBox: NSTextView!

let searchString = "2018-03-29 19:10:17"
let baseString = logTextBox.string

let findIndex = baseString.endIndex(of: searchString)!
print(findIndex)


// Function I got from the stack overflow link
extension StringProtocol where Index == String.Index {
func index<T: StringProtocol>(of string: T, options: String.CompareOptions = []) -> Index? {
    return range(of: string, options: options)?.lowerBound
}
func endIndex<T: StringProtocol>(of string: T, options: String.CompareOptions = []) -> Index? {
    return range(of: string, options: options)?.upperBound
}
func indexes<T: StringProtocol>(of string: T, options: String.CompareOptions = []) -> [Index] {
    var result: [Index] = []
    var start = startIndex
    while start < endIndex, let range = range(of: string, options: options, range: start..<endIndex) {
        result.append(range.lowerBound)
        start = range.lowerBound < range.upperBound ? range.upperBound : index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex
    }
    return result
}
func ranges<T: StringProtocol>(of string: T, options: String.CompareOptions = []) -> [Range<Index>] {
    var result: [Range<Index>] = []
    var start = startIndex
    while start < endIndex, let range = range(of: string, options: options, range: start..<endIndex) {
        result.append(range)
        start = range.lowerBound < range.upperBound  ? range.upperBound : index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex
    }
    return result
}
}

Solution

  • NSTextView is a subclass of NSText, and NSText defines a scrollRangeToVisible(NSRange) method. So you can use that for the scrolling part, but it requires an NSRange.

    There's a poorly-documented NSRange initializer that can convert a Range to an NSRange. So:

    let haystack = logTextBox.string
    let needle = "2018-03-29 19:10:17"
    if let needleRange = haystack.range(of: needle) {
        logTextBox.scrollRangeToVisible(NSRange(needleRange, in: haystack))
    }