NSRange в диапазоне
как я могу конвертировать NSRange to Range<String.Index> в Swift?
Я хочу использовать следующее UITextFieldDelegate способ:
func textField(textField: UITextField!,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String!) -> Bool {
textField.text.stringByReplacingCharactersInRange(???, withString: string)

12 ответов:
The
NSStringверсия (в отличие от Swift String)replacingCharacters(in: NSRange, with: NSString)принимаетNSRange, поэтому одно простое решение -преобразованиеStringдоNSStringпервый. Имена делегата и метода замены немного отличаются в Swift 3 и 2, поэтому в зависимости от того, какой Swift вы используете: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) }
по состоянию на Swift 4 (Xcode 9), стандарт Swift библиотека предоставляет методы для преобразования между диапазонами строк Swift (
Range<String.Index>) иNSStringдиапазоны (NSRange). Пример:let str = "abc" 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]) //поэтому замена текста в методе делегата текстового поля теперь можно сделать как
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) // ... } // ... }
(более старые ответы для Swift 3 и ранее:)
по состоянию на Swift 1.2,
String.Indexесть инициализаторinit?(_ utf16Index: UTF16Index, within characters: String), который может быть использован для преобразования
NSRangeдоRange<String.Index>правильно (включая все случаи эмодзи, региональных показателей или других расширенных кластеры графем) без промежуточного преобразования в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 } }этот метод возвращает дополнительно диапазон строк, потому что не все
NSRanges действительны для данной строки Swift.The
UITextFieldDelegateделегат метод может быть записан как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 }обратное преобразование
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) } }простой тест:
let str = "abc" 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)) //обновление для Swift 2:
Swift 2 версия
rangeFromNSRange()уже дали автор Сергей Яковенко в ответ, я это вот для полноты картины: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 2 версия
NSRangeFromRange()и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)) } }обновление для 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 } }пример:
let str = "abc" 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)) //
вы должны использовать
Range<String.Index>вместоNSRange. Как я это делаю (может быть, есть лучший способ), взяв строкуString.Indexперемещение его сadvance.Я не знаю, что вы пытаетесь заменить, но давайте представим, что вы хотите заменить первые 2 символа.
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)
ответ Мартином R кажется правильным, потому что он учитывает Unicode.
однако на момент публикации (Swift 1) его код не компилируется в Swift 2.0 (Xcode 7), потому что они удалены . Обновленная версия ниже:
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) } }
Это похоже на ответ Эмили, однако, так как вы спросили конкретно, как преобразовать
NSRangetoRange<String.Index>вы бы сделали что-то вроде этого: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) ... }
рифф на большой ответ @Emilie, а не замена/конкурирующий ответ.
(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)")выход:
start index: 4 end index: 7 range: 4..<7 original: This is a test final: !his is a testобратите внимание, что индексы учитывают несколько единиц кода. Флаг (региональный символ индикатора буквы ES) составляет 8 байт, а (лицо со слезами радости) - 4 байта. (В данном конкретном случае оказывается, что количество байтов одинаково для представлений UTF-8, UTF-16 и UTF-32.)
оборачивая его в a 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)")выход:
newString: !his is a test
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { let strString = ((textField.text)! as NSString).stringByReplacingCharactersInRange(range, withString: string) }
в Swift 2.0 предположив
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)
вот мои лучшие усилия. Но это не может проверить или обнаружить неверный входной аргумент.
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.результат.
подробности
NSRangeСNSStringподсчитывает UTF-16 код подразделенияы. ИRange<String.Index>от SWIFTStringэто непрозрачного относительный тип, который обеспечивает только операции равенства и навигации. Это намеренно скрытый дизайн.хотя
Range<String.Index>кажется, сопоставляется с кодовым блоком UTF-16 смещение, это просто деталь реализации, и я не мог найти никакого упоминания о какой-либо гарантии. Это означает, что детали реализации могут быть изменены в любое время. Внутреннее представление SwiftStringне очень определенными, и я не могу полагаться на него.
NSRangeзначения могут быть непосредственно сопоставлены сString.UTF16Viewиндексы. Но нет никакого метода, чтобы преобразовать его вString.Index.Свифт
String.Indexэто индекс для итерации SwiftCharacterчто это графема Юникод кластер. Затем, вы должны обеспечить правильноеNSRangeкоторый выбирает правильные кластеры графем. Если вы предоставите неправильный диапазон, как в приведенном выше примере, это приведет к неправильному результату, потому что правильный диапазон кластера графем не может быть вычислен.если есть гарантия, что
String.Indexи кодовое смещение UTF-16, тогда проблема становится простой. Но это вряд ли произойдет.обратное преобразование
в любом случае обратное преобразование может это должно быть сделано точно.
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))результат.
(0,6) (4,2)
Я нашел самое чистое решение swift2 только для создания категории на 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) } }а затем вызвать его из функции делегата текстового поля:
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 }
официальная документация Swift 3.0 beta предоставила свое стандартное решение для этой ситуации под названием строку.UTF16View в разделе элементы UTF16View соответствуют символам NSString title
в принятом ответе я нахожу опционные громоздкие. Это работает с Swift 3 и, кажется, не имеет никаких проблем с 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 }
Comments