Как использовать String.substringWithRange в Swift? Или как работают Range в Swift?
Краткий ответ: используйте 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.
Ответов пока нет.
Войдите чтобы оставить ответ