Avatar ·

Как использовать String.substringWithRange в Swift? Или как работают Range в Swift?

Привет всем! Я новичок в Swift, перехожу с Objective-C, и у меня возникла проблема с работой со строками. В Objective-C я легко вырезал подстроки с помощью `substringWithRange:`, например: ```objc NSString *str = @"Привет, мир!"; NSRange range = NSMakeRange(0, 6); NSString *substr = [str substringWithRange:range]; // "Привет" ``` В Swift я пытаюсь сделать то же самое, но сталкиваюсь с ошибками. Я написал: ```swift let str = "Привет, мир!" let range = NSMakeRange(0, 6) let substr = str.substringWithRange(range) // Ошибка! ``` Xcode говорит, что `String` не имеет метода `substringWithRange`. Я читал, что в Swift нужно использовать `Range`, но я не понимаю, как его создать. Почему нельзя просто передать `NSRange`? Пожалуйста, объясните на примерах: 1. Как правильно вырезать подстроку из строки в Swift? 2. Что такое `Range` и `String.Index` и как с ними работать? 3. Как обрабатывать строки с Unicode (например, эмодзи или кириллицу)? Заранее спасибо за помощь! <<>>

Краткий ответ: используйте String с Range<String.Index>

В Swift работа со строками основана на символах Unicode, поэтому индексы строк не являются простыми целыми числами. Вместо NSRange нужно использовать Range<String.Index>. Вот правильный способ вырезать подстроку:

let str = "Привет, мир!"
let startIndex = str.startIndex
let endIndex = str.index(startIndex, offsetBy: 6)
let range = startIndex..<endIndex
let substr = str[range] // "Привет"
print(substr) // Output: Привет

Подробное объяснение

В Swift String — это коллекция символов Unicode, где каждый символ может занимать разное количество байт. Поэтому индексация через целые числа (как в массивах) невозможна. Вместо этого используются String.Index — специальные значения, которые учитывают графем-кластеры (например, эмодзи "👍" — это один символ, но может состоять из нескольких Unicode-скаляров).

Основные методы для работы с индексами:

  • str.startIndex — индекс первого символа.
  • str.endIndex — индекс после последнего символа (не сам символ).
  • str.index(after: i) — индекс следующего символа после i.
  • str.index(before: i) — индекс предыдущего символа перед i.
  • str.index(i, offsetBy: n) — индекс, смещённый на n символов от i.

Примеры с разными сценариями:

// 1. Подстрока от начала до 6 символов
let str = "Привет, мир!"
let range1 = str.startIndex..<str.index(str.startIndex, offsetBy: 6)
print(str[range1]) // "Привет"

// 2. Подстрока с 8-го символа до конца
let range2 = str.index(str.startIndex, offsetBy: 8)..<str.endIndex
print(str[range2]) // "мир!"

// 3. Подстрока с 3-го по 10-й символ
let start = str.index(str.startIndex, offsetBy: 3)
let end = str.index(str.startIndex, offsetBy: 10)
let range3 = start..<end
print(str[range3]) // "вет, ми"

// 4. Работа с эмодзи (каждый эмодзи — один символ)
let emojiStr = "Hello 👍 World"
let emojiRange = emojiStr.index(emojiStr.startIndex, offsetBy: 6)..<emojiStr.index(emojiStr.startIndex, offsetBy: 7)
print(emojiStr[emojiRange]) // "👍"

Удобные методы (Swift 4+)

Начиная с Swift 4, появились более простые методы:

// prefix, suffix
let str = "Привет, мир!"
print(str.prefix(6))  // "Привет"
print(str.suffix(4))  // "мир!"

// dropFirst, dropLast
print(String(str.dropFirst(8))) // "мир!"
print(String(str.dropLast(5)))  // "Привет"

// subscript с ClosedRange
let rangeClosed = str.index(str.startIndex, offsetBy: 0)...str.index(str.startIndex, offsetBy: 5)
print(str[rangeClosed]) // "Привет"

Если нужно использовать NSRange (например, для совместимости)

Вы можете преобразовать NSRange в Range<String.Index>:

let nsRange = NSRange(location: 0, length: 6)
if let swiftRange = Range(nsRange, in: str) {
    let substr = str[swiftRange]
    print(substr) // "Привет"
}

Важно: NSRange работает с байтами или UTF-16-кодовыми единицами, поэтому при работе с Unicode-строками (эмодзи, кириллица) могут быть ошибки. Всегда предпочитайте Range<String.Index>.

<<>>

Альтернативный подход: использование NSString bridge

Если вам нужно быстро перенести код из Objective-C или вы работаете с API, требующими NSString, вы можете привести String к NSString:

let str = "Привет, мир!" as NSString
let nsRange = NSRange(location: 0, length: 6)
let substr = str.substring(with: nsRange) // "Привет"
print(substr)

Этот метод проще для тех, кто привык к Objective-C, но имеет недостатки:

  • Работает с UTF-16, поэтому для строк с эмодзи могут быть сюрпризы (например, эмодзи "👍" занимает 2 позиции в UTF-16).
  • Создаёт мост между Swift и Objective-C, что может снизить производительность.

Дополнение: работа с String.Index через расширение

Чтобы упростить код, можно добавить расширение для String:

extension String {
    func substring(from: Int, to: Int) -> String? {
        guard from >= 0, to <= self.count, from < to else { return nil }
        let start = self.index(self.startIndex, offsetBy: from)
        let end = self.index(self.startIndex, offsetBy: to)
        return String(self[start..<end])
    }
    
    func substring(range: NSRange) -> String? {
        guard let swiftRange = Range(range, in: self) else { return nil }
        return String(self[swiftRange])
    }
}

// Использование
let str = "Привет, мир!"
print(str.substring(from: 0, to: 6)!) // "Привет"
print(str.substring(range: NSRange(location: 8, length: 4))!) // "мир!"

Этот подход сочетает удобство целочисленных индексов с безопасностью Unicode.