Antworten:
Aktualisiert für Swift 4
Swift-Bereiche sind komplexer als NSRange
und wurden in Swift 3 nicht einfacher. Wenn Sie versuchen möchten, die Gründe für diese Komplexität zu verstehen, lesen Sie dies und das . Ich zeige Ihnen nur, wie Sie sie erstellen und wann Sie sie verwenden könnten.
a...b
Dieser Bereichsoperator erstellt einen Swift-Bereich, der sowohl Element a
als auch Element enthält b
, auch wenn dies b
der maximal mögliche Wert für einen Typ (wie Int.max
) ist. Es gibt zwei verschiedene Arten von geschlossenen Bereichen: ClosedRange
und CountableClosedRange
.
ClosedRange
Die Elemente aller Bereiche in Swift sind vergleichbar (dh sie entsprechen dem Comparable-Protokoll). Auf diese Weise können Sie aus einer Sammlung auf die Elemente im Bereich zugreifen. Hier ist ein Beispiel:
let myRange: ClosedRange = 1...3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]
A ClosedRange
ist jedoch nicht zählbar (dh es entspricht nicht dem Sequenzprotokoll). Das heißt, Sie können die Elemente nicht mit einer for
Schleife durchlaufen . Dafür brauchen Sie die CountableClosedRange
.
CountableClosedRange
Dies ähnelt dem letzten, außer dass der Bereich jetzt auch wiederholt werden kann.
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
Dieser Bereichsoperator enthält ein Element, a
jedoch kein Element b
. Wie oben gibt es zwei verschiedene Arten von halboffenen Bereichen: Range
und CountableRange
.
Range
Wie bei ClosedRange
können Sie mit einem auf die Elemente einer Sammlung zugreifen Range
. Beispiel:
let myRange: Range = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
Auch hier können Sie nicht über a iterieren, Range
da es nur vergleichbar und nicht schrittfähig ist.
CountableRange
A CountableRange
ermöglicht die Iteration.
let myRange: CountableRange = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
for index in myRange {
print(myArray[index])
}
Sie können (müssen) NSRange
manchmal noch in Swift verwenden ( z. B. wenn Sie zugeordnete Zeichenfolgen erstellen), daher ist es hilfreich zu wissen, wie man eine erstellt.
let myNSRange = NSRange(location: 3, length: 2)
Beachten Sie, dass dies Position und Länge ist , nicht Startindex und Endindex. Das Beispiel hier hat eine ähnliche Bedeutung wie der Swift-Bereich 3..<5
. Da die Typen jedoch unterschiedlich sind, sind sie nicht austauschbar.
Die Operatoren ...
und ..<
range sind eine Kurzform zum Erstellen von Bereichen. Beispielsweise:
let myRange = 1..<3
Der lange Weg, um den gleichen Bereich zu erstellen, wäre
let myRange = CountableRange<Int>(uncheckedBounds: (lower: 1, upper: 3)) // 1..<3
Sie können sehen, dass der Indextyp hier ist Int
. Dies funktioniert jedoch nicht String
, da Zeichenfolgen aus Zeichen bestehen und nicht alle Zeichen dieselbe Größe haben. (Lesen Sie dies für weitere Informationen.) Ein Emoji wie 😀 benötigt beispielsweise mehr Platz als der Buchstabe "b".
Problem mit NSRange
Versuchen Sie, mit NSRange
und NSString
mit Emoji zu experimentieren, und Sie werden sehen, was ich meine. Kopfschmerzen.
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"!?
Das Smiley-Gesicht benötigt zwei UTF-16-Codeeinheiten zum Speichern, sodass das unerwartete Ergebnis entsteht, dass das "d" nicht enthalten ist.
Schnelle Lösung
Aus diesem Grund verwenden Sie Swift Strings Range<String.Index>
nicht Range<Int>
. Der String-Index wird basierend auf einem bestimmten String berechnet, damit er weiß, ob Emoji oder erweiterte Graphemcluster vorhanden sind.
Beispiel
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...
und ...b
und..<b
In Swift 4 wurden die Dinge etwas vereinfacht. Wann immer der Start- oder Endpunkt eines Bereichs abgeleitet werden kann, können Sie ihn weglassen.
Int
Sie können einseitige Ganzzahlbereiche verwenden, um Sammlungen zu durchlaufen. Hier einige Beispiele aus der Dokumentation .
// 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...
String
Dies funktioniert auch mit String-Bereichen. Wenn Sie einen Bereich mit str.startIndex
oder str.endIndex
an einem Ende erstellen, können Sie ihn weglassen. Der Compiler wird darauf schließen.
Gegeben
var str = "Hello, playground"
let index = str.index(str.startIndex, offsetBy: 5)
let myRange = ..<index // Hello
Sie können mithilfe von vom Index zum str.endIndex wechseln ...
var str = "Hello, playground"
let index = str.index(str.endIndex, offsetBy: -10)
let myRange = index... // playground
Anmerkungen
NSString
speichert seine Zeichen intern in UTF-16-Codierung. Ein vollständiger Unicode-Skalar ist 21 Bit. Das grinsende Zeichen ( U+1F600
) kann nicht in einer einzelnen 16-Bit-Codeeinheit gespeichert werden, daher ist es auf 2 NSRange
Zählungen basierend auf 16-Bit-Codeeinheiten verteilt. In diesem Beispiel repräsentieren 3 Codeeinheiten zufällig nur 2 Zeichen
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
oder einfach
let myString = "Hello World"
let myRange = myString.startIndex..<myString.startIndex.advancedBy(5)
let mySubString = myString.substringWithRange(myRange) // Hello
Verwenden Sie so
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)
Ausgabe: Hallo
Ich finde es überraschend, dass es selbst in Swift 4 noch keine einfache native Möglichkeit gibt, einen String-Bereich mit Int auszudrücken. Die einzigen String-Methoden, mit denen Sie ein Int angeben können, um einen Teilstring nach Bereich zu erhalten, sind prefix
und suffix
.
Es ist nützlich, einige Konvertierungsprogramme zur Hand zu haben, damit wir wie NSRange sprechen können, wenn wir mit einem String sprechen. Hier ist ein Dienstprogramm, das genau wie NSRange einen Speicherort und eine Länge annimmt und Folgendes zurückgibt 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
}
Zum Beispiel "hello".range(0,1)"
ist das Range<String.Index>
Umarmen des ersten Charakters von "hello"
. Als Bonus habe ich auszuschließende Standorte erlaubt: "hello".range(-1,1)"
die Range<String.Index>
das letzte Zeichen umfassen "hello"
.
Es ist auch nützlich, a Range<String.Index>
in einen NSRange zu konvertieren , wenn Sie mit Cocoa sprechen müssen (z. B. im Umgang mit NSAttributedString-Attributbereichen). Swift 4 bietet eine native Möglichkeit, dies zu tun:
let nsrange = NSRange(range, in:s) // where s is the string
Wir können also ein anderes Dienstprogramm schreiben, bei dem wir direkt von einem String-Speicherort und einer String-Länge zu einem NSRange wechseln:
extension String {
func nsRange(_ start:Int, _ length:Int) -> NSRange {
return NSRange(self.range(start,length), in:self)
}
}
Ich habe die folgende Erweiterung erstellt:
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
}
}
}
Anwendungsbeispiel:
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
Sie können so verwenden
let nsRange = NSRange(location: someInt, length: someInt)
wie in
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
Ich möchte das machen:
print("Hello"[1...3])
// out: Error
Aber leider kann ich keinen eigenen Index schreiben, weil der Abscheuliche den Namensraum einnimmt.
Wir können dies jedoch tun:
print("Hello"[range: 1...3])
// out: ell
Fügen Sie dies einfach Ihrem Projekt hinzu:
extension String {
subscript(range: ClosedRange<Int>) -> String {
get {
let start = String.Index(utf16Offset: range.lowerBound, in: self)
let end = String.Index(utf16Offset: range.upperBound, in: self)
return String(self[start...end])
}
}
}
Range<String.Index>
, aber manchmal ist es notwendig, mitNSString
und zu arbeitenNSRange
, sodass etwas mehr Kontext nützlich wäre. - Schauen Sie sich aber stackoverflow.com/questions/24092884/… an .