Avatar ·

Как проверить, содержит ли строка другую строку в Swift?

Всем привет! Я новичок в Swift, перехожу с Objective-C, и никак не могу разобраться с работой со строками. В Objective-C я использовал `rangeOfString:` для проверки вхождения подстроки, например: ```objc NSString *string = @"hello Swift"; if ([string rangeOfString:@"Swift"].location != NSNotFound) { NSLog(@"Строка содержит 'Swift'"); } ``` Но в Swift я вижу, что строки — это структуры, а не объекты, и у них нет метода `rangeOfString`. Я пробовал использовать `containsString`, но компилятор ругается, что такого метода нет. Написал такой код: ```swift let string = "hello Swift" if string.containsString("Swift") { print("Строка содержит 'Swift'") } ``` Получаю ошибку: `Value of type 'String' has no member 'containsString'`. Погуглил, говорят, что в Swift 3 и выше метод переименовали, но я не могу понять, как правильно. Я использую Xcode 11 и Swift 5. Также интересует, как проверять вхождение с учётом регистра (case-insensitive) и как искать подстроку с определённой позиции. Буду благодарен за подробный ответ с примерами для новичка. Заранее спасибо! <<>>

Основное решение: метод contains(_:)

В Swift (начиная с версии 3.0) для проверки, содержит ли строка другую строку, используется метод contains(_:). Он возвращает true, если подстрока найдена, и false в противном случае. Вот как исправить ваш код:

let string = "hello Swift"
if string.contains("Swift") {
    print("Строка содержит 'Swift'")
} else {
    print("Строка не содержит 'Swift'")
}
// Вывод: Строка содержит 'Swift'

Метод contains(_:) является частью протокола StringProtocol, поэтому он работает как с String, так и с Substring. Он чувствителен к регистру по умолчанию.

Проверка без учёта регистра (case-insensitive)

Если вам нужно найти подстроку независимо от регистра, используйте метод localizedCaseInsensitiveContains(_:) или преобразуйте обе строки к одному регистру:

let string = "Hello Swift"
let substring = "swift"

// Вариант 1: использование встроенного метода
if string.localizedCaseInsensitiveContains(substring) {
    print("Строка содержит 'swift' (без учёта регистра)")
}

// Вариант 2: приведение к нижнему регистру
if string.lowercased().contains(substring.lowercased()) {
    print("То же самое, но через lowercased()")
}
// Вывод: Строка содержит 'swift' (без учёта регистра)

Метод localizedCaseInsensitiveContains(_:) учитывает локализацию (например, турецкую букву 'i'), в отличие от простого lowercased(). Для большинства случаев используйте его.

Поиск с указанием позиции (range)

Если вам нужно не просто проверить наличие, но и узнать, где именно находится подстрока, используйте метод range(of:). Он возвращает опциональный Range<String.Index>:

let string = "hello Swift, hello world"
if let range = string.range(of: "hello") {
    print("Первое вхождение 'hello' найдено на позиции: \(range.lowerBound)")
    // range.lowerBound — это String.Index, можно преобразовать в Int
    let startIndex = string.distance(from: string.startIndex, to: range.lowerBound)
    print("Числовой индекс: \(startIndex)")
} else {
    print("Подстрока не найдена")
}
// Вывод: Первое вхождение 'hello' найдено на позиции: Index(_rawBits: 0)
// Числовой индекс: 0

Чтобы найти все вхождения, используйте цикл:

let string = "hello Swift, hello world"
let searchString = "hello"
var searchRange = string.startIndex..<string.endIndex

while let range = string.range(of: searchString, range: searchRange) {
    let startIndex = string.distance(from: string.startIndex, to: range.lowerBound)
    print("Найдено '\(searchString)' на позиции: \(startIndex)")
    searchRange = range.upperBound..<string.endIndex
}
// Вывод:
// Найдено 'hello' на позиции: 0
// Найдено 'hello' на позиции: 13

Поиск с учётом регистра и локализованное сравнение

Метод range(of:) также поддерживает опции сравнения:

let string = "Hello Swift"
let substring = "swift"

// Без учёта регистра
if let range = string.range(of: substring, options: .caseInsensitive) {
    print("Найдено '\(substring)' без учёта регистра")
}

// Локализованное сравнение (например, для немецкого 'ß' -> 'ss')
if let range = string.range(of: "swift", options: [.caseInsensitive, .diacriticInsensitive]) {
    print("Найдено с игнорированием диакритических знаков")
}

// Обратный поиск (с конца строки)
if let range = string.range(of: "o", options: .backwards) {
    let index = string.distance(from: string.startIndex, to: range.lowerBound)
    print("Последнее вхождение 'o' на позиции: \(index)")
}
// Вывод: Последнее вхождение 'o' на позиции: 4

Проверка префикса и суффикса

Для частных случаев проверки начала или конца строки используйте:

let string = "Hello Swift"

if string.hasPrefix("Hello") {
    print("Строка начинается с 'Hello'")
}

if string.hasSuffix("Swift") {
    print("Строка заканчивается на 'Swift'")
}
// Вывод: оба условия истинны

Рекомендации

  • Для простой проверки используйте contains(_:) — он самый быстрый и читаемый.
  • Для поиска без учёта регистра — localizedCaseInsensitiveContains(_:).
  • Для получения позиции подстроки — range(of:) с опциями.
  • Избегайте использования Objective-C методов (rangeOfString) в чистом Swift коде — они работают, но менее эффективны и не идиоматичны.

Надеюсь, это поможет! Помните, что строки в Swift — это не просто массивы символов, а полноценные коллекции с Unicode-поддержкой, поэтому индексы работают не как целые числа, а как специальные String.Index.

<<>>

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

Если вы переходите с Objective-C и чувствуете себя неуверенно с новым API, вы можете использовать мост между String и NSString. Swift автоматически мостит строки, поэтому вы можете вызывать методы NSString напрямую:

let string = "hello Swift" as NSString
if string.range(of: "Swift").location != NSNotFound {
    print("Строка содержит 'Swift' (через NSString)")
}
// Вывод: Строка содержит 'Swift' (через NSString)

Однако этот подход не рекомендуется для нового кода, так как:

  • Он менее производительный из-за моста.
  • Не использует преимущества Swift (типобезопасность, Unicode-корректность).
  • Методы NSString работают с NSRange, а не с Range<String.Index>, что может вызвать путаницу.

Использование регулярных выражений

Для сложных шаблонов поиска используйте NSRegularExpression:

import Foundation

let string = "Hello Swift 5.0"
let pattern = "Swift\\s\\d+\\.\\d+" // поиск "Swift" + версия

if let regex = try? NSRegularExpression(pattern: pattern) {
    let range = NSRange(string.startIndex..., in: string)
    if regex.firstMatch(in: string, range: range) != nil {
        print("Найдено совпадение с шаблоном")
    }
}
// Вывод: Найдено совпадение с шаблоном

Это мощный инструмент, но для простых проверок он избыточен. Используйте его только когда contains(_:) или range(of:) недостаточно.

Проверка на пустую строку

Если вы ищете пустую строку, contains("") всегда вернёт true, так как пустая строка считается подстрокой любой строки. Учитывайте это в логике:

let string = "hello"
let empty = ""
if string.contains(empty) {
    print("Любая строка содержит пустую подстроку") // всегда true
}

В целом, придерживайтесь нативных методов Swift