Avatar ·

Как преобразовать NSRange в Range<String.Index> в Swift для работы с NSString?

Всем привет! Я только начал изучать Swift и столкнулся с проблемой при работе с текстом. У меня есть NSString, и я получаю диапазон (NSRange) после поиска подстроки с помощью метода range(of:). Но когда я пытаюсь использовать этот диапазон для извлечения подстроки из Swift String, компилятор выдаёт ошибку. Вот мой код: ```swift let nsString: NSString = "Привет, мир! Hello, world!" let searchRange = nsString.range(of: "мир") // searchRange имеет тип NSRange let swiftString: String = nsString as String // Пытаюсь получить подстроку let substring = swiftString[searchRange] // Ошибка! ``` Ошибка говорит, что `NSRange` не может быть использован как индекс для `String`. Я читал, что в Swift строки индексируются через `String.Index`, а не через целые числа или `NSRange`. Как мне правильно преобразовать `NSRange` в `Range`, чтобы работать с Swift String? Нужно ли учитывать многобайтовые символы (например, эмодзи или кириллицу)? Буду благодарен за примеры кода! Использую Swift 5, iOS 15. <<>>

Проблема: разные типы индексов

В Swift строки (String) — это коллекции символов Unicode, и индексы у них не целочисленные, а основанные на графемах. NSRange из Objective-C использует целые числа (location и length), которые не подходят напрямую для Swift String. Поэтому нужно преобразовать NSRange в Range<String.Index>.

Решение: метод range(from:)

Самый простой способ — использовать метод Range<String.Index>? = String.range(from: NSRange), который доступен через мост с NSString. Но его нет напрямую у String. Вместо этого мы можем получить String.UTF16View или работать через NSString. Вот универсальный подход:

import Foundation

let nsString: NSString = "Привет, мир! Hello, world!"
let searchRange = nsString.range(of: "мир")

// Преобразуем NSRange в Range<String.Index>
if let swiftRange = Range<String.Index>(searchRange, in: nsString as String) {
    let substring = (nsString as String)[swiftRange]
    print(substring) // "мир"
} else {
    print("Диапазон недействителен")
}

Объяснение

  • Range<String.Index>(searchRange, in: string) — это инициализатор, который принимает NSRange и строку, для которой нужно создать диапазон. Он автоматически корректно обрабатывает многобайтовые символы (кириллица, эмодзи).
  • Если NSRange выходит за пределы строки, инициализатор вернёт nil — поэтому используем if let.
  • Метод работает как с ASCII, так и с Unicode.

Пример с эмодзи

let emojiString: NSString = "😀👍🏼🔥"
let range = emojiString.range(of: "👍🏼")
if let swiftRange = Range(range, in: emojiString as String) {
    let substring = (emojiString as String)[swiftRange]
    print(substring) // "👍🏼"
}

Альтернативный способ: через UTF16View

Если вы работаете с чистой Swift String (без NSString), можно использовать utf16Offset:

let swiftString = "Привет, мир!"
let nsRange = NSRange(location: 8, length: 3)
if let start = String.Index(utf16Offset: nsRange.location, in: swiftString),
   let end = String.Index(utf16Offset: nsRange.location + nsRange.length, in: swiftString) {
    let substring = swiftString[start..<end]
    print(substring) // "мир"
}

Когда это нужно?

  • При интеграции с Objective-C кодом (UIKit, NSAttributedString, NSRegularExpression).
  • При работе с текстовыми полями (UITextField, UITextView), где делегаты возвращают NSRange.
  • При парсинге текста с помощью Foundation-методов.

Важно

Убедитесь, что NSRange получен из той же строки, которую вы преобразуете. Если строка была изменена, диапазон может стать недействительным. Всегда проверяйте результат через if let или guard let.

<<>>

Прямое использование NSRange с NSString (если не нужна Swift String)

Если вам не обязательно преобразовывать результат в Swift String, вы можете остаться в мире NSString и использовать метод substring(with:):

let nsString: NSString = "Привет, мир! Hello, world!"
let searchRange = nsString.range(of: "мир")
if searchRange.location != NSNotFound {
    let substring = nsString.substring(with: searchRange)
    print(substring) // "мир"
}

Этот подход проще и быстрее, так как не требует преобразования типов. Он безопасен для многобайтовых символов, потому что NSString тоже работает с Unicode.

Когда использовать этот вариант?

  • Когда вы работаете с Objective-C API, которые возвращают NSString.
  • Если вам не нужны возможности Swift String (например, map, filter).
  • В критичных по производительности участках (преобразование типов имеет накладные расходы).

Недостаток

Вы теряете преимущества Swift String: безопасность типов, поддержка протоколов Collection, Sequence и т.д. Если в дальнейшем вы будете активно манипулировать строкой (например, искать другие подстроки, изменять регистр), лучше преобразовать в Swift String с помощью первого ответа.