В Objective-c мы создаем диапазон, используя NSRange
NSRange range;
Итак, как создать диапазон в Swift?
В Objective-c мы создаем диапазон, используя NSRange
NSRange range;
Итак, как создать диапазон в Swift?
Обновлен для Swift 3
Сдвиговые диапазоны более сложны, чем NSRange
, и они не стали легче в Swift 3. Если вы хотите попытаться понять причину некоторой сложности, прочитайте this и this. Я просто покажу вам, как их создавать и когда вы можете их использовать.
a...b
ClosedRange
Элементы всех диапазонов в Swift сопоставимы (т.е. соответствуют протоколу Comparable). Это позволяет вам получить доступ к элементам в диапазоне от коллекции. Вот пример:
let myRange: ClosedRange = 1...3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]
Однако a ClosedRange
не является счетным (т.е. не соответствует протоколу Sequence). Это означает, что вы не можете перебирать элементы с помощью цикла for
. Для этого вам понадобится CountableClosedRange
.
CountableClosedRange
Это похоже на последнее, за исключением того, что теперь диапазон также можно повторить.
let myRange: CountableClosedRange = 1...3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]
for index in myRange {
print(myArray[index])
}
a..<b
Этот оператор диапазона содержит элемент a
, но не элемент b
. Как и выше, существуют два разных типа полуоткрытых диапазонов: Range
и CountableRange
.
Range
Как и в случае с ClosedRange
, вы можете получить доступ к элементам коллекции с помощью Range
. Пример:
let myRange: Range = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
Опять же, вы не можете перебирать Range
, потому что это только сопоставимо, а не просто.
CountableRange
A CountableRange
допускает итерацию.
let myRange: CountableRange = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
for index in myRange {
print(myArray[index])
}
Вы можете (обязательно) использовать NSRange
порой в Swift (например, при создании атрибутных строк), поэтому полезно знать, как сделать один.
let myNSRange = NSRange(location: 3, length: 2)
Обратите внимание, что это местоположение и длина, а не индекс индекса и конца. Пример здесь похож по значению на диапазон Swift 3..<5
. Однако, поскольку типы различны, они не взаимозаменяемы.
Операторы диапазона ...
и ..<
являются сокращенным способом создания диапазонов. Например:
let myRange = 1..<3
Длинный способ создания одного и того же диапазона будет
let myRange = CountableRange<Int>(uncheckedBounds: (lower: 1, upper: 3)) // 1..<3
Вы можете видеть, что тип индекса здесь Int
. Это не работает для String
, хотя, поскольку строки состоят из символов, а не все символы одного размера. (Прочтите этот для получения дополнительной информации.) Эможи вроде 😀, например, занимает больше места, чем буква "b".
Проблема с NSRange
Попробуйте поэкспериментировать с NSRange
и NSString
с emoji, и вы поймете, что я имею в виду. Головная боль.
let myNSRange = NSRange(location: 1, length: 3)
let myNSString: NSString = "abcde"
myNSString.substring(with: myNSRange) // "bcd"
let myNSString2: NSString = "a😀cde"
myNSString2.substring(with: myNSRange) // "😀c" Where is the "d"!?
У смайликов есть два кодовых блока UTF-16, поэтому он дает неожиданный результат, не включая "d".
Быстрое решение
Из-за этого с Swift Strings вы используете Range<String.Index>
, а не Range<Int>
. Индекс String рассчитывается на основе определенной строки, чтобы он знал, существуют ли какие-либо кластеры emoji или расширенные графемы.
Пример
var myString = "abcde"
let start = myString.index(myString.startIndex, offsetBy: 1)
let end = myString.index(myString.startIndex, offsetBy: 4)
let myRange = start..<end
myString.substring(with: myRange) // "bcd"
myString = "a😀cde"
let start2 = myString.index(myString.startIndex, offsetBy: 1)
let end2 = myString.index(myString.startIndex, offsetBy: 4)
let myRange2 = start2..<end2
myString.substring(with: myRange2) // "😀cd"
См. также:
Примечания
Xcode 8 beta 2 • Swift 3
let myString = "Hello World"
let myRange = myString.startIndex..<myString.index(myString.startIndex, offsetBy: 5)
let mySubString = myString.substring(with: myRange) // Hello
Xcode 7 • Swift 2.0
let myString = "Hello World"
let myRange = Range<String.Index>(start: myString.startIndex, end: myString.startIndex.advancedBy(5))
let mySubString = myString.substringWithRange(myRange) // Hello
или просто
let myString = "Hello World"
let myRange = myString.startIndex..<myString.startIndex.advancedBy(5)
let mySubString = myString.substringWithRange(myRange) // Hello
(1.. < 10)
возвращает...
Диапазон = 1.. < 10
Используйте это как
var start = str.startIndex // Start at the string start index
var end = advance(str.startIndex, 5) // Take start index and advance 5 characters forward
var range: Range<String.Index> = Range<String.Index>(start: start,end: end)
let firstFiveDigit = str.substringWithRange(range)
print(firstFiveDigit)
Вывод: Привет
Мне кажется удивительным, что даже в Swift 4 все еще нет простого родного способа выражения диапазона строк с использованием Int. Единственными методами String, которые позволяют вам снабжать Int как способ получения подстроки по диапазону, являются prefix
и suffix
.
Полезно иметь под рукой некоторые утилиты преобразования, чтобы мы могли говорить, как NSRange, когда говорим со строкой. Здесь утилита, которая принимает местоположение и длину, как и NSRange, и возвращает Range<String.Index>
:
func range(_ start:Int, _ length:Int) -> Range<String.Index> {
let i = self.index(start >= 0 ? self.startIndex : self.endIndex,
offsetBy: start)
let j = self.index(i, offsetBy: length)
return i..<j
}
Например, "hello".range(0,1)"
- это Range<String.Index>
, охватывающий первый символ "hello"
. В качестве бонуса я допустил отрицательные местоположения: "hello".range(-1,1)"
- это Range<String.Index>
, охватывающий последний символ "hello"
.
Полезно также преобразовать a Range<String.Index>
в NSRange, для тех моментов, когда вам нужно поговорить с Cocoa (например, при работе с диапазонами атрибутов NSAttributedString). Swift 4 обеспечивает собственный способ сделать это:
let nsrange = NSRange(range, in:s) // where s is the string
Таким образом, мы можем написать еще одну утилиту, в которой мы переходим непосредственно из местоположения String и длины в NSRange:
extension String {
func nsRange(_ start:Int, _ length:Int) -> NSRange {
return NSRange(self.range(start,length), in:self)
}
}
Вы можете использовать это как
let nsRange = NSRange(location: someInt, length: someInt)
как в
let myNSString = bigTOTPCode as NSString //12345678
let firstDigit = myNSString.substringWithRange(NSRange(location: 0, length: 1)) //1
let secondDigit = myNSString.substringWithRange(NSRange(location: 1, length: 1)) //2
let thirdDigit = myNSString.substringWithRange(NSRange(location: 2, length: 4)) //3456
Я создал следующее расширение:
extension String {
func substring(from from:Int, to:Int) -> String? {
if from<to && from>=0 && to<self.characters.count {
let rng = self.startIndex.advancedBy(from)..<self.startIndex.advancedBy(to)
return self.substringWithRange(rng)
} else {
return nil
}
}
}
пример использования:
print("abcde".substring(from: 1, to: 10)) //nil
print("abcde".substring(from: 2, to: 4)) //Optional("cd")
print("abcde".substring(from: 1, to: 0)) //nil
print("abcde".substring(from: 1, to: 1)) //nil
print("abcde".substring(from: -1, to: 1)) //nil
func replace(input: String, start: Int,lenght: Int, newChar: Character) -> String {
var chars = Array(input.characters)
for i in start...lenght {
guard i < input.characters.count else{
break
}
chars[i] = newChar
}
return String(chars)
}
Если кто-то хочет создать объект NSRange, его можно создать как:
let range: NSRange = NSRange.init(location: 0, length: 5)
это создаст диапазон с позицией 0 и длиной 5