Como posso converter NSRange
para Range<String.Index>
no Swift?
Eu quero usar o seguinte UITextFieldDelegate
método:
func textField(textField: UITextField!,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String!) -> Bool {
textField.text.stringByReplacingCharactersInRange(???, withString: string)
Respostas:
A
NSString
versão (em oposição à Swift String) dereplacingCharacters(in: NSRange, with: NSString)
aceita umNSRange
, portanto, uma solução simples é converterString
para oNSString
primeiro . Os nomes dos métodos de delegação e substituição são ligeiramente diferentes no Swift 3 e 2, portanto, dependendo do Swift que você estiver usando:Swift 3.0
Swift 2.x
fonte
textField
contiver caracteres unicode de unidade com vários códigos, como emoji. Como é um campo de entrada, o usuário pode digitar emoji (😂), outros caracteres da página 1 de caracteres com múltiplos códigos de unidade, como bandeiras (🇪🇸).UITextFieldDelegate
otextField:shouldChangeCharactersInRange:replacementString:
método não fornece um intervalo que começa ou termina dentro de um caractere unicode, pois o retorno de chamada é baseado em um usuário digitando caracteres do teclado e não é possível digitar apenas parte de um caractere unicode emoji. Observe que se o delegado fosse escrito em Obj-C, ele teria o mesmo problema.(textField.text as NSString?)?.stringByReplacingCharactersInRange(range, withString: string)
A partir do Swift 4 (Xcode 9), a biblioteca padrão do Swift fornece métodos para converter entre os intervalos de string do Swift (
Range<String.Index>
) e osNSString
intervalos (NSRange
). Exemplo:Portanto, a substituição de texto no método delegado do campo de texto agora pode ser feita como
(Respostas anteriores para Swift 3 e anteriores :)
A partir do Swift 1.2,
String.Index
tem um inicializadorque pode ser utilizado para converter
NSRange
aRange<String.Index>
correctamente (incluindo todos os casos de Emojis, Indicadores da região ou outros agregados estendidos) grafema sem conversão para um intermediárioNSString
:Esse método retorna um intervalo opcional de cadeias porque nem todos os
NSRange
s são válidos para uma determinada cadeia Swift.O
UITextFieldDelegate
método delegado pode então ser escrito comoA conversão inversa é
Um teste simples:
Atualização para o Swift 2:
A versão Swift 2 do
rangeFromNSRange()
já foi dada por Serhii Yakovenko nesta resposta , eu a incluo aqui por completo:A versão do Swift 2
NSRangeFromRange()
éAtualização para Swift 3 (Xcode 8):
Exemplo:
fonte
range
será(0,2)
porqueNSRange
se refere aos caracteres UTF-16 em umNSString
. Mas conta como um caractere Unicode em uma string Swift. - alet end = advance(start, range.length)
partir da resposta mencionada falha nesse caso com a mensagem de erro "erro fatal: não é possível incrementar endIndex".String
para saltar para a referência da API, leia todos os métodos e comentários, em seguida, tentar coisas diferentes até que ele funciona :)let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex)
, eu acho que você realmente quer limitá-lo porutf16.endIndex - 1
. Caso contrário, você pode começar o final da string.Você precisa usar em
Range<String.Index>
vez do clássicoNSRange
. O jeito que eu faço (talvez exista uma maneira melhor) é pegar a corda eString.Index
movê-laadvance
.Não sei qual intervalo você está tentando substituir, mas vamos fingir que deseja substituir os dois primeiros caracteres.
fonte
Esta resposta de Martin R parece estar correta porque é responsável por Unicode.
No entanto, no momento da publicação (Swift 1), seu código não é compilado no Swift 2.0 (Xcode 7), porque eles removeram a
advance()
função. A versão atualizada está abaixo:Swift 2
Swift 3
Swift 4
fonte
Isso é semelhante à resposta de Emilie, no entanto, uma vez que você perguntou especificamente como converter o
NSRange
paraRange<String.Index>
você faria algo assim:fonte
NSRange
fala, embora seus componentes sejam inteiros) na visualização de caracteres da sequência, que pode falhar quando usada em uma sequência com "caracteres" que levam mais de uma unidade UTF16 para expressar.Um riff sobre a ótima resposta de @Emilie, não uma resposta substituta / concorrente.
(Xcode6-Beta5)
Resultado:
Observe que os índices são responsáveis por várias unidades de código. O sinalizador (LETRAS DE SÍMBOLOS DO INDICADOR REGIONAL ES) é de 8 bytes e o (CARA COM LÁGRIMAS DE ALEGRIA) é de 4 bytes. (Nesse caso específico, verifica-se que o número de bytes é o mesmo para as representações UTF-8, UTF-16 e UTF-32.)
Envolvendo-o em uma função:
Resultado:
fonte
fonte
No Swift 2.0, assumindo
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
:fonte
Aqui está o meu melhor esforço. Mas isso não pode verificar ou detectar argumentos de entrada incorretos.
Resultado.
Detalhes
NSRange
dasNSString
contagens de unidades de código UTF-16 . ERange<String.Index>
do SwiftString
é um tipo relativo opaco que fornece apenas operações de igualdade e navegação. Este é um design intencionalmente oculto.Embora
Range<String.Index>
pareça estar mapeado para o deslocamento da unidade de código UTF-16, isso é apenas um detalhe da implementação, e não encontrei nenhuma menção a nenhuma garantia. Isso significa que os detalhes da implementação podem ser alterados a qualquer momento. A representação interna do SwiftString
não está bem definida, e não posso confiar nela.NSRange
valores podem ser mapeados diretamente paraString.UTF16View
índices. Mas não há método para convertê-loString.Index
.Swift
String.Index
é o índice para iterar o Swift,Character
que é um cluster de grafema Unicode . Em seguida, você deve fornecer o apropriado,NSRange
que seleciona os clusters de grafema corretos. Se você fornecer um intervalo errado, como no exemplo acima, ele produzirá um resultado errado, pois não foi possível descobrir o intervalo adequado do cluster grafema.Se houver uma garantia de que o deslocamento da unidade de código
String.Index
é UTF-16, o problema se tornará simples. Mas é improvável que isso aconteça.Conversão inversa
De qualquer forma, a conversão inversa pode ser feita com precisão.
Resultado.
fonte
return NSRange(location: a.utf16Count, length: b.utf16Count)
deve ser alterado para #return NSRange(location: a.utf16.count, length: b.utf16.count)
Descobri que a única solução mais limpa do swift2 é criar uma categoria no NSRange:
E, em seguida, chame-o de para a função delegada do campo de texto:
fonte
A documentação oficial do Swift 3.0 beta forneceu sua solução padrão para esta situação sob o título String.UTF16View na seção UTF16View Elements Match NSString Characters title
fonte
Na resposta aceita, acho os opcionais pesados. Isso funciona com o Swift 3 e parece não ter problemas com emojis.
fonte
fonte