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