Como criar alcance em Swift?

104

Em Objective-c, criamos intervalo usando NSRange

NSRange range;

Então, como criar alcance no Swift?

Vichhai
fonte
3
Para que você precisa do intervalo? Strings swift usam Range<String.Index>, mas às vezes é necessário trabalhar com NSStringe NSRange, então um pouco mais de contexto seria útil. - Mas dê uma olhada em stackoverflow.com/questions/24092884/… .
Martin R

Respostas:

258

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.

Faixas fechadas: a...b

Este operador de intervalo cria um intervalo Swift que inclui elemento a e elemento b, mesmo se bfor o valor máximo possível para um tipo (como Int.max). Existem dois tipos diferentes de intervalos fechados: ClosedRangee CountableClosedRange.

1 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 ClosedRangenã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 forloop. Para isso, você precisa do CountableClosedRange.

2 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])
}

Faixas semi-abertas: a..<b

Este operador de intervalo inclui elemento, amas não elemento b. Como acima, existem dois tipos diferentes de intervalos semiabertos: Rangee CountableRange.

1 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 Rangeporque ele é apenas comparável, não passível de stridging.

2 CountableRange

A CountableRangepermite 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])
}

NSRange

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.

Faixas com cordas

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 NSRangee NSStringcom 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"

Intervalos de um lado: a...e ...be..<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.startIndexou str.endIndexem 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

Veja também:

Notas

  • Você não pode usar um intervalo que você criou com uma string em uma string diferente.
  • Como você pode ver, os intervalos de String são uma dor no Swift, mas eles o tornam possível para lidar melhor com emoji e outros escalares Unicode.

Um estudo mais aprofundado

Suragch
fonte
Meu comentário se refere à subseção "Problema com NSRange". NSStringarmazena 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. NSRangecontagens baseadas em unidades de código de 16 bits. Neste exemplo, 3 unidades de código representam apenas 2 caracteres
Código diferente de
25

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
Leo Dabus
fonte
3

(1 .. <10)

retorna ...

Intervalo = 1 .. <10

Victor Lin
fonte
Como isso responde à pergunta?
Machavity
2

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á

Dharmbir Singh
fonte
Vejo que eles têm o tipo de dados "Range". Então, como posso usar isso?
vichhai
@vichhai Você pode explicar mais onde deseja usar?
Dharmbir Singh
Como no objetivo c: intervalo NSRange;
vichhai
1

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 prefixe 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)
    }
}
mate
fonte
1

Se alguém quiser criar um objeto NSRange pode criar como:

let range: NSRange = NSRange.init(location: 0, length: 5)

isso criará um intervalo com posição 0 e comprimento 5

furgão
fonte
1
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)
}
Abo3atef
fonte
0

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
dr OX
fonte
0

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
Mohammad Nurdin
fonte
0

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])
        }
    }
}
ScottyBlades
fonte