En Objective-c, nous créons une plage en utilisant NSRange
NSRange range;
Alors, comment créer une gamme dans Swift?
mis à jour pour Swift 4
Les gammes Swift sont plus complexes que NSRange
et ne sont pas devenues plus simples dans Swift 3. Si vous voulez essayer de comprendre le raisonnement qui sous-tend une partie de cette complexité, lisez ceci et ceci . Je vais juste vous montrer comment les créer et quand vous pourriez les utiliser.
a...b
Cet opérateur de plage crée une plage Swift comprenant les deux éléments a
et élément b
, même si b
est la valeur maximale possible pour un type (comme Int.max
). Il existe deux types de plages fermées: ClosedRange
et CountableClosedRange
.
ClosedRange
Les éléments de toutes les plages de Swift sont comparables (c’est-à-dire qu’ils sont conformes au protocole Comparable). Cela vous permet d'accéder aux éléments de la plage à partir d'une collection. Voici un exemple:
let myRange: ClosedRange = 1...3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]
Cependant, un ClosedRange
n'est pas comptable (c'est-à-dire qu'il n'est pas conforme au protocole de séquence). Cela signifie que vous ne pouvez pas parcourir les éléments avec une boucle for
. Pour cela, vous avez besoin de la CountableClosedRange
.
CountableClosedRange
Ceci est similaire au dernier sauf que maintenant, la plage peut également être itérée.
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
Cet opérateur de plage inclut l'élément a
mais et non l'élément b
. Comme ci-dessus, il existe deux types de plages semi-ouvertes: Range
et CountableRange
.
Range
Comme avec ClosedRange
, vous pouvez accéder aux éléments d'une collection avec un Range
. Exemple:
let myRange: Range = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
Encore une fois, cependant, vous ne pouvez pas parcourir un Range
car il est seulement comparable, pas stridable.
CountableRange
Un CountableRange
permet l'itération.
let myRange: CountableRange = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
for index in myRange {
print(myArray[index])
}
Vous pouvez (devez) toujours utiliser NSRange
parfois dans Swift (lors de la création de chaînes attribuées , par exemple), il est donc utile de savoir comment en créer une.
let myNSRange = NSRange(location: 3, length: 2)
Notez qu'il s'agit d'emplacement et de longueur et non d'index de début et d'index de fin. L'exemple présenté ici a une signification similaire à la plage Swift _ 3..<5
. Cependant, étant donné que les types sont différents, ils ne sont pas interchangeables.
Les opérateurs de plage ...
et ..<
constituent un raccourci pour créer des plages. Par exemple:
let myRange = 1..<3
Le moyen long pour créer la même gamme serait
let myRange = CountableRange<Int>(uncheckedBounds: (lower: 1, upper: 3)) // 1..<3
Vous pouvez voir que le type d'index ici est Int
. Cela ne fonctionne pas pour String
, car les chaînes sont constituées de caractères et tous les caractères ne sont pas de la même taille. (Lire this pour plus d'informations.) Un emoji comme ???? prend par exemple plus d'espace que la lettre "b".
Problème avec NSRange
Essayez d’expérimenter avec NSRange
et un NSString
avec emoji et vous verrez ce que je veux dire. Mal de tête.
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"!?
Le smiley prend deux unités de code UTF-16 à stocker, ce qui donne le résultat inattendu de ne pas inclure le "d".
Solution rapide
Pour cette raison, avec Swift Chaînes, vous utilisez Range<String.Index>
, pas Range<Int>
. L'indice de chaîne est calculé en fonction d'une chaîne particulière afin qu'il sache s'il existe des grappes d'emoji ou de graphèmes étendus.
Exemple
var myString = "abcde"
let start = myString.index(myString.startIndex, offsetBy: 1)
let end = myString.index(myString.startIndex, offsetBy: 4)
let myRange = start..<end
myString[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[myRange2] // "????cd"
a...
et ...b
et ..<b
Dans Swift 4, les choses ont été simplifiées un peu. Chaque fois que le point de départ ou d'arrivée d'une plage peut être déduit, vous pouvez le laisser.
Int
Vous pouvez utiliser des plages entières unilatérales pour parcourir des collections. Voici quelques exemples tirés de la documentation .
// iterate from index 2 to the end of the array
for name in names[2...] {
print(name)
}
// iterate from the beginning of the array to index 2
for name in names[...2] {
print(name)
}
// iterate from the beginning of the array up to but not including index 2
for name in names[..<2] {
print(name)
}
// the range from negative infinity to 5. You can't iterate forward
// over this because the starting point in unknown.
let range = ...5
range.contains(7) // false
range.contains(4) // true
range.contains(-1) // true
// You can iterate over this but it will be an infinate loop
// so you have to break out at some point.
let range = 5...
Chaîne
Cela fonctionne aussi avec les gammes String. Si vous créez une plage avec str.startIndex
ou str.endIndex
à une extrémité, vous pouvez la laisser désactivée. Le compilateur l'inférera.
Donné
var str = "Hello, playground"
let index = str.index(str.startIndex, offsetBy: 5)
let myRange = ..<index // Hello
Vous pouvez passer de l'index à str.endIndex en utilisant ...
var str = "Hello, playground"
let index = str.index(str.endIndex, offsetBy: -10)
let myRange = index... // playground
Notes
Xcode 8 beta 2 • Swift
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.
let myString = "Hello World"
let myRange = Range<String.Index>(start: myString.startIndex, end: myString.startIndex.advancedBy(5))
let mySubString = myString.substringWithRange(myRange) // Hello
ou simplement
let myString = "Hello World"
let myRange = myString.startIndex..<myString.startIndex.advancedBy(5)
let mySubString = myString.substringWithRange(myRange) // Hello
(1 .. <10)
résultats...
Plage = 1 .. <10
Utiliser comme ça
var start = str.startIndex // Start at the string's 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)
Sortie: Bonjour
Si quelqu'un veut créer un objet NSRange, il peut créer:
let range: NSRange = NSRange.init(location: 0, length: 5)
cela va créer une plage avec la position 0 et la longueur 5
Je trouve surprenant que, même dans Swift 4, il n’existe toujours pas de moyen natif simple d’exprimer une plage de chaînes à l’aide de Int. Les seules méthodes String permettant de fournir un int pour obtenir une sous-chaîne par plage sont prefix
et suffix
.
Il est utile de disposer d’utilitaires de conversion pour pouvoir parler comme NSRange lorsqu’on parle à une chaîne. Voici un utilitaire qui prend un emplacement et une longueur, comme NSRange, et retourne un 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
}
Par exemple, "hello".range(0,1)"
est le Range<String.Index>
englobant le premier caractère de "hello"
. En bonus, j'ai autorisé les emplacements négatifs: "hello".range(-1,1)"
est le Range<String.Index>
englobant le dernier caractère de "hello"
.
Il est également utile de convertir un Range<String.Index>
en un NSRange, pour les moments où vous devez parler à Cocoa (par exemple, lorsque vous traitez avec des plages d'attributs NSAttributedString). Swift 4 fournit un moyen natif de le faire:
let nsrange = NSRange(range, in:s) // where s is the string
Nous pouvons donc écrire un autre utilitaire où nous allons directement d’un emplacement String et d’une longueur à un NSRange:
extension String {
func nsRange(_ start:Int, _ length:Int) -> NSRange {
return NSRange(self.range(start,length), in:self)
}
}
J'ai créé l'extension suivante:
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
}
}
}
exemple d'utilisation:
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
Vous pouvez utiliser comme ça
let nsRange = NSRange(location: someInt, length: someInt)
un péché
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
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)
}