¿Cómo puedo convertir NSRange
a Range<String.Index>
en Swift?
Quiero usar el siguiente UITextFieldDelegate
método:
func textField(textField: UITextField!,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String!) -> Bool {
textField.text.stringByReplacingCharactersInRange(???, withString: string)
Respuestas:
La
NSString
versión (a diferencia de Swift String) dereplacingCharacters(in: NSRange, with: NSString)
acepta unNSRange
, por lo que una solución simple es convertirString
aNSString
primero . Los nombres de los métodos de delegado y reemplazo son ligeramente diferentes en Swift 3 y 2, por lo que, dependiendo de qué Swift esté usando:Swift 3.0
Swift 2.x
fuente
textField
contiene caracteres unicode de unidad de código múltiple como emoji. Dado que es un campo de entrada, un usuario puede ingresar emoji (😂), otros caracteres de la página 1 de caracteres de unidades de múltiples códigos, como banderas (🇪🇸).UITextFieldDelegate
eltextField:shouldChangeCharactersInRange:replacementString:
método no proporcionará un rango que comience o termine dentro de un carácter unicode, ya que la devolución de llamada se basa en que un usuario ingresa caracteres desde el teclado, y no es posible escribir solo una parte de un personaje emoji unicode. Tenga en cuenta que si el delegado se escribiera en Obj-C, tendría el mismo problema.(textField.text as NSString?)?.stringByReplacingCharactersInRange(range, withString: string)
A partir de Swift 4 (Xcode 9), la biblioteca estándar de Swift proporciona métodos para convertir entre rangos de cadenas Swift (
Range<String.Index>
) yNSString
rangos (NSRange
). Ejemplo:Por lo tanto, el reemplazo de texto en el método de delegado de campo de texto ahora se puede hacer como
(Respuestas anteriores para Swift 3 y anteriores :)
A partir de Swift 1.2,
String.Index
tiene un inicializadorque se puede utilizar para convertir
NSRange
aRange<String.Index>
correctamente (incluyendo todos los casos de Emojis, Indicadores Regionales u otras agrupaciones de grafema prolongados) sin conversión intermedia a unaNSString
:Este método devuelve un rango de cadena opcional porque no todos los
NSRange
s son válidos para una cadena Swift determinada.El
UITextFieldDelegate
método delegado se puede escribir comoLa conversión inversa es
Una prueba simple:
Actualización para Swift 2:
La versión de Swift 2
rangeFromNSRange()
ya fue dada por Serhii Yakovenko en esta respuesta , la incluyo aquí para completar:La versión Swift 2 de
NSRangeFromRange()
esActualización para Swift 3 (Xcode 8):
Ejemplo:
fuente
range
es(0,2)
porque seNSRange
refiere a los caracteres UTF-16 en unNSString
. Pero cuenta como un personaje Unicode en una cadena Swift. -let end = advance(start, range.length)
de la respuesta referenciada se bloquea en este caso con el mensaje de error "error fatal: no se puede incrementar endIndex".String
para saltar a la referencia de la API, leer todos los métodos y comentarios, luego probar diferentes cosas hasta que funcione :)let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex)
, creo que realmente quieres limitarloutf16.endIndex - 1
. De lo contrario, puede comenzar desde el final de la cadena.Necesita usar en
Range<String.Index>
lugar del clásicoNSRange
. La forma en que lo hago (tal vez hay una mejor manera) es tomando la cuerdaString.Index
y moviéndolaadvance
.No sé qué rango está tratando de reemplazar, pero supongamos que desea reemplazar los primeros 2 caracteres.
fuente
Esta respuesta de Martin R parece ser correcta porque representa Unicode.
Sin embargo, en el momento de la publicación (Swift 1) su código no se compila en Swift 2.0 (Xcode 7), porque eliminaron la
advance()
función. La versión actualizada está abajo:Swift 2
Swift 3
Swift 4
fuente
Sin embargo, esto es similar a la respuesta de Emilie, ya que usted preguntó específicamente cómo convertirlo
NSRange
aRange<String.Index>
usted haría algo como esto:fuente
NSRange
habla, aunque sus componentes son enteros) contra la vista de caracteres de la cadena, que puede fallar cuando se usa en una cadena con "caracteres" que requieren más de una unidad UTF16 para expresarse.Un riff sobre la gran respuesta de @Emilie, no una respuesta de reemplazo / competencia.
(Xcode6-Beta5)
Salida:
Observe que los índices representan múltiples unidades de código. La bandera (LETRAS DE SÍMBOLO DEL INDICADOR REGIONAL ES) tiene 8 bytes y la (CARA CON LÁGRIMAS DE ALEGRÍA) tiene 4 bytes. (En este caso particular, resulta que el número de bytes es el mismo para las representaciones UTF-8, UTF-16 y UTF-32).
Envolviéndolo en una función:
Salida:
fuente
fuente
En Swift 2.0 asumiendo
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
:fuente
Aquí está mi mejor esfuerzo. Pero esto no puede verificar o detectar un argumento de entrada incorrecto.
Resultado.
Detalles
NSRange
de losNSString
recuentos de unidades de código UTF-16 . YRange<String.Index>
de SwiftString
es un opaco tipo relativo que proporciona solo operaciones de igualdad y navegación. Este es un diseño intencionalmente oculto.Aunque
Range<String.Index>
parece estar asignado al desplazamiento de la unidad de código UTF-16, eso es solo un detalle de implementación, y no pude encontrar ninguna mención sobre ninguna garantía. Eso significa que los detalles de implementación se pueden cambiar en cualquier momento. La representación interna de SwiftString
no está bastante definida, y no puedo confiar en ella.NSRange
los valores se pueden asignar directamente a losString.UTF16View
índices. Pero no hay ningún método para convertirlo enString.Index
.Swift
String.Index
es un índice para iterar Swift,Character
que es un clúster de grafema Unicode . Luego, debe proporcionar el adecuadoNSRange
que selecciona los grupos de grafemas correctos. Si proporciona un rango incorrecto como en el ejemplo anterior, producirá un resultado incorrecto porque no se pudo determinar el rango adecuado del grupo de grafemas.Si hay una garantía de que el
String.Index
es UTF-16-unidad de desplazamiento de código, a continuación, se convierte en un problema sencillo. Pero es poco probable que suceda.Conversión inversa
De todos modos, la conversión inversa se puede hacer con precisión.
Resultado.
fuente
return NSRange(location: a.utf16Count, length: b.utf16Count)
debe cambiarse areturn NSRange(location: a.utf16.count, length: b.utf16.count)
He encontrado que la única solución más limpia de swift2 es crear una categoría en NSRange:
Y luego llame desde la función de delegado de campo de texto:
fuente
La documentación oficial de Swift 3.0 beta ha proporcionado su solución estándar para esta situación bajo el título String.UTF16View en la sección UTF16View Elements Match NSString Characters title
fuente
En la respuesta aceptada encuentro los opcionales engorrosos. Esto funciona con Swift 3 y parece no tener problemas con los emojis.
fuente
fuente