Convert a Swift Range to Use String.Index

The Situation

I was working on a SwiftUI app where I wanted to select text in a text view and insert some text at the start of the selection. SwiftUI’s native text editor currently does not support getting the selection range so I had to wrap an AppKit text view and store the selection range when the selection changes.

func textViewDidChangeSelection(_ notification: Notification) {
  guard let textView = notification.object as? 
    NSTextView else {
            
    return
  }
  control.model.selectionRange = 
    Range.init(textView.selectedRange())
}

The Problem

The code in the previous example gives me a Swift range, which uses integers, Range<Int>. I wanted to use the selection range’s start index as the destination to insert the string.

model.text.insert(contentsOf: textToInsert, 
  at: model.selectionRange?.startIndex)

But the compiler gave me an error. The reason for the error is the String struct’s insert method takes a range that uses string indexes, Range<String.Index>, not integers.

The Solution

The solution is to create a string index for the string and use that as the argument to the insert method. The base for the string index is the string’s start index. The offset for the string index is the selection range’s lower bound.

let insertionPoint = model.text.index(model.text.startIndex, 
  offsetBy: model.selectionRange?.lowerBound ?? 0)
model.text.insert(contentsOf: textToInsert, at: insertionPoint)

Get the Swift Dev Journal Newsletter

Subscribe and get exclusive articles, a free guide on moving from tutorials to making your first app, notices of sales on books, and anything I decide to add in the future.

    We won't send you spam. Unsubscribe at any time.