Respostas:
Atualizado para Swift 4
Intervalos de Swift são mais complexos do que NSRange
, e eles não ficaram mais fáceis no Swift 3. Se você quiser tentar entender o raciocínio por trás de alguma desta complexidade, leia isto e isto . Vou apenas mostrar como criá-los e quando você pode usá-los.
a...b
Este operador de intervalo cria um intervalo Swift que inclui elemento a
e elemento b
, mesmo se b
for o valor máximo possível para um tipo (como Int.max
). Existem dois tipos diferentes de intervalos fechados: ClosedRange
e CountableClosedRange
.
ClosedRange
Os elementos de todas as faixas em Swift são comparáveis (ou seja, eles estão em conformidade com o protocolo Comparable). Isso permite que você acesse os elementos no intervalo de uma coleção. Aqui está um exemplo:
let myRange: ClosedRange = 1...3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]
No entanto, a ClosedRange
não é contável (ou seja, não está em conformidade com o protocolo de Sequência). Isso significa que você não pode iterar sobre os elementos com um for
loop. Para isso, você precisa do CountableClosedRange
.
CountableClosedRange
É semelhante ao anterior, exceto que agora o intervalo também pode ser iterado.
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
Este operador de intervalo inclui elemento, a
mas não elemento b
. Como acima, existem dois tipos diferentes de intervalos semiabertos: Range
e CountableRange
.
Range
Assim como com ClosedRange
, você pode acessar os elementos de uma coleção com a Range
. Exemplo:
let myRange: Range = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
Novamente, porém, você não pode iterar sobre a Range
porque ele é apenas comparável, não passível de stridging.
CountableRange
A CountableRange
permite a iteração.
let myRange: CountableRange = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
for index in myRange {
print(myArray[index])
}
Você ainda pode (deve) usar NSRange
às vezes em Swift (ao fazer strings atribuídas , por exemplo), por isso é útil saber como fazer uma.
let myNSRange = NSRange(location: 3, length: 2)
Observe que este é o local e o comprimento , não o índice inicial e o índice final. O exemplo aqui é semelhante ao significado da faixa Swift 3..<5
. No entanto, como os tipos são diferentes, eles não são intercambiáveis.
Os operadores de intervalo ...
e ..<
são uma forma abreviada de criar intervalos. Por exemplo:
let myRange = 1..<3
O longo caminho para criar o mesmo intervalo seria
let myRange = CountableRange<Int>(uncheckedBounds: (lower: 1, upper: 3)) // 1..<3
Você pode ver que o tipo de índice aqui é Int
. Isso não funciona String
, porque Strings são feitos de caracteres e nem todos os caracteres têm o mesmo tamanho. (Leia isto para obter mais informações.) Um emoji como 😀, por exemplo, ocupa mais espaço do que a letra "b".
Problema com NSRange
Experimente experimentar com NSRange
e NSString
com emoji e você verá o que quero dizer. Dor de cabeça.
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"!?
O rosto sorridente precisa de duas unidades de código UTF-16 para armazenar, então dá o resultado inesperado de não incluir o "d".
Solução rápida
Por causa disso, com Swift Strings você usa Range<String.Index>
, não Range<Int>
. O Índice de Sequência é calculado com base em uma sequência específica para que ele saiba se há algum emoji ou agrupamento de grafemas estendidos.
Exemplo
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...
e ...b
e..<b
No Swift 4 as coisas foram um pouco simplificadas. Sempre que o ponto inicial ou final de um intervalo pode ser inferido, você pode deixá-lo desativado.
Int
Você pode usar intervalos inteiros de um lado para iterar nas coleções. Aqui estão alguns exemplos da documentação .
// 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...
Corda
Isso também funciona com intervalos de String. Se estiver fazendo um intervalo com str.startIndex
ou str.endIndex
em uma extremidade, você pode deixá-lo desativado. O compilador irá inferir isso.
Dado
var str = "Hello, playground"
let index = str.index(str.startIndex, offsetBy: 5)
let myRange = ..<index // Hello
Você pode ir do índice para str.endIndex usando ...
var str = "Hello, playground"
let index = str.index(str.endIndex, offsetBy: -10)
let myRange = index... // playground
Notas
NSString
armazena internamente seus caracteres na codificação UTF-16. Um escalar unicode completo tem 21 bits. O caractere de rosto sorridente ( U+1F600
) não pode ser armazenado em uma única unidade de código de 16 bits, portanto, é distribuído por 2. NSRange
contagens baseadas em unidades de código de 16 bits. Neste exemplo, 3 unidades de código representam apenas 2 caracteres
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
ou simplesmente
let myString = "Hello World"
let myRange = myString.startIndex..<myString.startIndex.advancedBy(5)
let mySubString = myString.substringWithRange(myRange) // Hello
Usar assim
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)
Resultado: Olá
Acho surpreendente que, mesmo no Swift 4, ainda não haja uma maneira nativa simples de expressar um intervalo de String usando Int. Os únicos métodos String que permitem fornecer um Int como forma de obter uma substring por intervalo são prefix
e suffix
.
É útil ter em mãos alguns utilitários de conversão, para que possamos falar como NSRange ao falar com uma String. Este é um utilitário que pega um local e comprimento, assim como NSRange, e retorna um 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
}
Por exemplo, "hello".range(0,1)"
é o Range<String.Index>
abraçando o primeiro personagem de "hello"
. Como um bônus, permiti locais negativos: "hello".range(-1,1)"
é o Range<String.Index>
abraço do último personagem de"hello"
.
É útil também converter a Range<String.Index>
em NSRange, para aqueles momentos em que você precisa falar com Cocoa (por exemplo, ao lidar com intervalos de atributos NSAttributedString). O Swift 4 oferece uma maneira nativa de fazer isso:
let nsrange = NSRange(range, in:s) // where s is the string
Podemos, portanto, escrever outro utilitário onde vamos diretamente de um local e comprimento de String para um NSRange:
extension String {
func nsRange(_ start:Int, _ length:Int) -> NSRange {
return NSRange(self.range(start,length), in:self)
}
}
Criei a seguinte extensão:
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
}
}
}
exemplo de uso:
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
Você pode usar assim
let nsRange = NSRange(location: someInt, length: someInt)
como em
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
Eu quero fazer isso:
print("Hello"[1...3])
// out: Error
Mas, infelizmente, não posso escrever um subscrito próprio porque o odiado ocupa o espaço do nome.
Podemos fazer isso no entanto:
print("Hello"[range: 1...3])
// out: ell
Basta adicionar ao seu projeto:
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>
, mas às vezes é necessário trabalhar comNSString
eNSRange
, então um pouco mais de contexto seria útil. - Mas dê uma olhada em stackoverflow.com/questions/24092884/… .