nsstring swift ios8 nsrange

nsstring - NSRange to Range<String.Index>



swift ios8 (13)

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> ) y rangos NSRange ( NSRange ). Ejemplo:

let str = "a馃懣b馃嚛馃嚜c" let r1 = str.range(of: "馃嚛馃嚜")! // String range to NSRange: let n1 = NSRange(r1, in: str) print((str as NSString).substring(with: n1)) // 馃嚛馃嚜 // NSRange back to String range: let r2 = Range(n1, in: str)! print(str[r2]) // 馃嚛馃嚜

Por lo tanto, la sustituci贸n de texto en el m茅todo de delegado de campo de texto ahora se puede hacer como

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if let oldString = textField.text { let newString = oldString.replacingCharacters(in: Range(range, in: oldString)!, with: string) // ... } // ... }

(Respuestas anteriores para Swift 3 y anteriores :)

A partir de Swift 1.2, String.Index tiene un inicializador

init?(_ utf16Index: UTF16Index, within characters: String)

que se puede usar para convertir NSRange a Range<String.Index> correctamente (incluidos todos los casos de Emojis, indicadores regionales u otros cl煤steres de grafemas extendidos) sin conversi贸n intermedia a una NSString :

extension String { func rangeFromNSRange(nsRange : NSRange) -> Range<String.Index>? { let from16 = advance(utf16.startIndex, nsRange.location, utf16.endIndex) let to16 = advance(from16, nsRange.length, utf16.endIndex) if let from = String.Index(from16, within: self), let to = String.Index(to16, within: self) { return from ..< to } return nil } }

Este m茅todo devuelve un rango de cadena opcional porque no todos los NSRange s son v谩lidos para una cadena Swift dada.

El m茅todo delegado de UITextFieldDelegate se puede escribir como

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { if let swRange = textField.text.rangeFromNSRange(range) { let newString = textField.text.stringByReplacingCharactersInRange(swRange, withString: string) // ... } return true }

La conversi贸n inversa es

extension String { func NSRangeFromRange(range : Range<String.Index>) -> NSRange { let utf16view = self.utf16 let from = String.UTF16View.Index(range.startIndex, within: utf16view) let to = String.UTF16View.Index(range.endIndex, within: utf16view) return NSMakeRange(from - utf16view.startIndex, to - from) } }

Una prueba simple:

let str = "a馃懣b馃嚛馃嚜c" let r1 = str.rangeOfString("馃嚛馃嚜")! // String range to NSRange: let n1 = str.NSRangeFromRange(r1) println((str as NSString).substringWithRange(n1)) // 馃嚛馃嚜 // NSRange back to String range: let r2 = str.rangeFromNSRange(n1)! println(str.substringWithRange(r2)) // 馃嚛馃嚜

Actualizaci贸n para Swift 2:

La versi贸n Swift 2 de rangeFromNSRange() ya fue dada por Serhii Yakovenko en esta respuesta , la estoy incluyendo aqu铆 para completar:

extension String { func rangeFromNSRange(nsRange : NSRange) -> Range<String.Index>? { let from16 = utf16.startIndex.advancedBy(nsRange.location, limit: utf16.endIndex) let to16 = from16.advancedBy(nsRange.length, limit: utf16.endIndex) if let from = String.Index(from16, within: self), let to = String.Index(to16, within: self) { return from ..< to } return nil } }

La versi贸n Swift 2 de NSRangeFromRange() es

extension String { func NSRangeFromRange(range : Range<String.Index>) -> NSRange { let utf16view = self.utf16 let from = String.UTF16View.Index(range.startIndex, within: utf16view) let to = String.UTF16View.Index(range.endIndex, within: utf16view) return NSMakeRange(utf16view.startIndex.distanceTo(from), from.distanceTo(to)) } }

Actualizaci贸n para Swift 3 (Xcode 8):

extension String { func nsRange(from range: Range<String.Index>) -> NSRange { let from = range.lowerBound.samePosition(in: utf16) let to = range.upperBound.samePosition(in: utf16) return NSRange(location: utf16.distance(from: utf16.startIndex, to: from), length: utf16.distance(from: from, to: to)) } } extension String { func range(from nsRange: NSRange) -> Range<String.Index>? { guard let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex), let to16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location + nsRange.length, limitedBy: utf16.endIndex), let from = from16.samePosition(in: self), let to = to16.samePosition(in: self) else { return nil } return from ..< to } }

Ejemplo:

let str = "a馃懣b馃嚛馃嚜c" let r1 = str.range(of: "馃嚛馃嚜")! // String range to NSRange: let n1 = str.nsRange(from: r1) print((str as NSString).substring(with: n1)) // 馃嚛馃嚜 // NSRange back to String range: let r2 = str.range(from: n1)! print(str.substring(with: r2)) // 馃嚛馃嚜

驴C贸mo puedo convertir NSRange al Range<String.Index> en Swift?

Quiero usar el siguiente m茅todo UITextFieldDelegate :

func textField(textField: UITextField!, shouldChangeCharactersInRange range: NSRange, replacementString string: String!) -> Bool { textField.text.stringByReplacingCharactersInRange(???, withString: string)


Aqu铆 est谩 mi mejor esfuerzo. Pero esto no puede verificar o detectar un argumento de entrada incorrecto.

extension String { /// :r: Must correctly select proper UTF-16 code-unit range. Wrong range will produce wrong result. public func convertRangeFromNSRange(r:NSRange) -> Range<String.Index> { let a = (self as NSString).substringToIndex(r.location) let b = (self as NSString).substringWithRange(r) let n1 = distance(a.startIndex, a.endIndex) let n2 = distance(b.startIndex, b.endIndex) let i1 = advance(startIndex, n1) let i2 = advance(i1, n2) return Range<String.Index>(start: i1, end: i2) } } let s = "馃嚜馃嚫馃槀" println(s[s.convertRangeFromNSRange(NSRange(location: 4, length: 2))]) // Proper range. Produces correct result. println(s[s.convertRangeFromNSRange(NSRange(location: 0, length: 4))]) // Proper range. Produces correct result. println(s[s.convertRangeFromNSRange(NSRange(location: 0, length: 2))]) // Improper range. Produces wrong result. println(s[s.convertRangeFromNSRange(NSRange(location: 0, length: 1))]) // Improper range. Produces wrong result.

Resultado.

馃槀 馃嚜馃嚫 馃嚜馃嚫 馃嚜馃嚫

Detalles

NSRange de NSString cuenta las NSString de c贸digo UTF-16. Y el Range<String.Index> de Swift String es un tipo relativo opaco que proporciona solo operaciones de igualdad y navegaci贸n. Esto es intencionalmente dise帽o oculto.

Aunque el Range<String.Index> parece estar asignado a un desplazamiento de unidad de c贸digo UTF-16, eso es solo un detalle de la implementaci贸n, y no pude encontrar ninguna menci贸n sobre ninguna garant铆a. Eso significa que los detalles de la implementaci贸n se pueden cambiar en cualquier momento. La representaci贸n interna de Swift String no est谩 bastante definida, y no puedo confiar en ella.

NSRange valores de NSRange se pueden asignar directamente a los 铆ndices String.UTF16View . Pero no hay un m茅todo para convertirlo en String.Index .

Swift String.Index es un 铆ndice para iterar el Character Swift que es un cl煤ster de grafema Unicode . Luego, debe proporcionar un NSRange adecuado que seleccione los grupos de grafemas correctos. Si proporciona un rango incorrecto como el ejemplo anterior, producir谩 un resultado incorrecto porque no se pudo determinar el rango correcto del grupo de grafemas.

Si hay una garant铆a de que el String.Index es el desplazamiento de la unidad de c贸digo UTF-16, entonces el problema se vuelve simple. Pero es poco probable que suceda.

Conversi贸n inversa

De todos modos la conversi贸n inversa se puede hacer con precisi贸n.

extension String { /// O(1) if `self` is optimised to use UTF-16. /// O(n) otherwise. public func convertRangeToNSRange(r:Range<String.Index>) -> NSRange { let a = substringToIndex(r.startIndex) let b = substringWithRange(r) return NSRange(location: a.utf16Count, length: b.utf16Count) } } println(convertRangeToNSRange(s.startIndex..<s.endIndex)) println(convertRangeToNSRange(s.startIndex.successor()..<s.endIndex))

Resultado.

(0,6) (4,2)


En Swift 2.0 asumiendo func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { :

var oldString = textfield.text! let newRange = oldString.startIndex.advancedBy(range.location)..<oldString.startIndex.advancedBy(range.location + range.length) let newString = oldString.stringByReplacingCharactersInRange(newRange, withString: string)


En la respuesta aceptada encuentro las opciones engorrosas. Esto funciona con Swift 3 y parece que no tiene problemas con los emojis.

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { guard let value = textField.text else {return false} // there may be a reason for returning true in this case but I can''t think of it // now value is a String, not an optional String let valueAfterChange = (value as NSString).replacingCharacters(in: range, with: string) // valueAfterChange is a String, not an optional String // now do whatever processing is required return true // or false, as required }


He encontrado que la 煤nica soluci贸n m谩s limpia de swift2 es crear una categor铆a en NSRange:

extension NSRange { func stringRangeForText(string: String) -> Range<String.Index> { let start = string.startIndex.advancedBy(self.location) let end = start.advancedBy(self.length) return Range<String.Index>(start: start, end: end) } }

Y luego ll谩melo desde para la funci贸n de delegado de campo de texto:

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { let range = range.stringRangeForText(textField.text) let output = textField.text.stringByReplacingCharactersInRange(range, withString: string) // your code goes here.... return true }


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 characters


La versi贸n NSString (a diferencia de Swift String) de la replacingCharacters(in: NSRange, with: NSString) acepta un NSRange , por lo que una soluci贸n simple es convertir primero String en NSString . 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茅s usando:

Swift 3.0

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { let nsString = textField.text as NSString? let newString = nsString?.replacingCharacters(in: range, with: string) }

Swift 2.x

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { let nsString = textField.text as NSString? let newString = nsString?.stringByReplacingCharactersInRange(range, withString: string) }


Sin embargo, esto es similar a la respuesta de Emilie, ya que usted pregunt贸 espec铆ficamente c贸mo convertir NSRange a Range<String.Index> har铆a algo como esto:

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { let start = advance(textField.text.startIndex, range.location) let end = advance(start, range.length) let swiftRange = Range<String.Index>(start: start, end: end) ... }


Un riff sobre la gran respuesta de @Emilie, no una respuesta de reemplazo / competencia.
(Xcode6-Beta5)

var original = "馃嚜馃嚫馃槀This is a test" var replacement = "!" var startIndex = advance(original.startIndex, 1) // Start at the second character var endIndex = advance(startIndex, 2) // point ahead two characters var range = Range(start:startIndex, end:endIndex) var final = original.stringByReplacingCharactersInRange(range, withString:replacement) println("start index: /(startIndex)") println("end index: /(endIndex)") println("range: /(range)") println("original: /(original)") println("final: /(final)")

Salida:

start index: 4 end index: 7 range: 4..<7 original: 馃嚜馃嚫馃槀This is a test final: 馃嚜馃嚫!his is a test

Observe que los 铆ndices cuentan para m煤ltiples unidades de c贸digo. La bandera (LISTA DE S脥MBOLOS DE INDICADOR REGIONAL) es de 8 bytes y la (CARA CON TIROS DE ALEGR脥A) es de 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 func:

func replaceString(#string:String, #with:String, #start:Int, #length:Int) ->String { var startIndex = advance(original.startIndex, start) // Start at the second character var endIndex = advance(startIndex, length) // point ahead two characters var range = Range(start:startIndex, end:endIndex) var final = original.stringByReplacingCharactersInRange(range, withString: replacement) return final } var newString = replaceString(string:original, with:replacement, start:1, length:2) println("newString:/(newString)")

Salida:

newString: !his is a test


Range<String.Index> usar Range<String.Index> lugar del NSRange cl谩sico. La forma en que lo hago (tal vez hay una mejor manera) es tomando el String.Index la cadena y movi茅ndolo con advance .

No s茅 qu茅 rango intenta reemplazar, pero supongamos que desea reemplazar los primeros 2 caracteres.

var start = textField.text.startIndex // Start at the string''s start index var end = advance(textField.text.startIndex, 2) // Take start index and advance 2 characters forward var range: Range<String.Index> = Range<String.Index>(start: start,end: end) textField.text.stringByReplacingCharactersInRange(range, withString: string)


Esta respuesta de Martin R parece ser correcta porque da cuenta de Unicode.

Sin embargo, en el momento de la publicaci贸n (Swift 1) su c贸digo no se compila en Swift 2.0 (Xcode 7), ya que eliminaron la funci贸n advance() . La versi贸n actualizada est谩 abajo:

Swift 2

extension String { func rangeFromNSRange(nsRange : NSRange) -> Range<String.Index>? { let from16 = utf16.startIndex.advancedBy(nsRange.location, limit: utf16.endIndex) let to16 = from16.advancedBy(nsRange.length, limit: utf16.endIndex) if let from = String.Index(from16, within: self), let to = String.Index(to16, within: self) { return from ..< to } return nil } }

Swift 3

extension String { func rangeFromNSRange(nsRange : NSRange) -> Range<String.Index>? { if let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex), let to16 = utf16.index(from16, offsetBy: nsRange.length, limitedBy: utf16.endIndex), let from = String.Index(from16, within: self), let to = String.Index(to16, within: self) { return from ..< to } return nil } }

Swift 4

extension String { func rangeFromNSRange(nsRange : NSRange) -> Range<String.Index>? { return Range(nsRange, in: self) } }


extension StringProtocol where Index == String.Index { func nsRange(of string: String) -> NSRange? { guard let range = self.range(of: string) else { return nil } return NSRange(range, in: self) } }


func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { let strString = ((textField.text)! as NSString).stringByReplacingCharactersInRange(range, withString: string) }