NSString 和 Swift.String 上的 replaceCharactersInRange 有区别吗

Is there a difference between the replaceCharactersInRange on NSString and Swift.String

我正在为 UITextField 编写一个验证器,我得到了一个现有字符串、替换字符串和一个进行替换的 NSRange。我有两个版本的代码来获取新的候选字符串:

一个。显式使用 NSString

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    let existingString: NSString = textField.text ?? ""
    let candidateString =  existingString.stringByReplacingCharactersInRange(range, withString: string)
    //do validation on candidateString here
    return true
}

b。生成一个 Range<String.Int> 并将其用于 Swift 字符串的 stringByReplacingCharactersInRange

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    let existingString = textField.text ?? ""
    let swiftRange = advance(existingString.startIndex, range.length)..<advance(existingString.startIndex, range.length) // I hate this.
    let candidateString = existingString.stringByReplacingCharactersInRange(swiftRange, withString: string)

因此,抛开生成 Swift 范围的代码不可读这一事实……这两种方法在执行上是否有所不同。具体来说,Swift 是否有一个更好的(可能更精通 unicode)版本的 stringByReplacingCharactersInRange,我通过避免转换为 NSString 而获得?

是的。两种方法之间存在差异,第二种方法更正确。它还能正确处理多码字符:

var str  = "Hello , playground"
let range = NSMakeRange(0, 8)
let swiftRange = advance(str.startIndex, range.location)..<advance(str.startIndex, range.length) // I hate this.
var newString = str.stringByReplacingCharactersInRange(swiftRange, withString: "goodbye")
// => goodbye playground
newString = (str as NSString).stringByReplacingCharactersInRange(range, withString: "goodbye")
// => "goodbye, playground"

我假设这是因为范围计算得更好,而不是因为替换方法有不同的实现。 swiftRange 是 0..<15

NSString

NSRange 等同于 String.UTF16.IndexRange(又名 String.UTF16Index)。

正确的方法(没有NSString)是:

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    let existingString = textField.text ?? ""

    // convert `NSRange` to `Range<String.Index>`
    let start = String.UTF16Index(range.location)
    let end = start.advancedBy(range.length)
    let swiftRange = start.samePositionIn(string)! ..< end.samePositionIn(string)!

    let candidateString = existingString.stringByReplacingCharactersInRange(swiftRange, withString: string)

    // Do validation...

    return true
}

您可以扩展 NSRange:

extension NSRange {
    func toStringRangeIn(string: String) -> Range<String.Index>? {
        let start = String.UTF16Index(self.location)
        let end = start.advancedBy(self.length)
        if let start = start.samePositionIn(string), end = end.samePositionIn(string) {
            return start ..< end
        }
        return nil
    }
}

var str  = "Hello "
let range = NSMakeRange(8, 4)
str.stringByReplacingCharactersInRange(range.toStringRangeIn(str)!, withString: "FOO") // -> "Hello FOO"
(str as NSString).stringByReplacingCharactersInRange(range, withString: "FOO")  // -> "Hello FOO"

有了这个,您可以:

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    let existingString = textField.text ?? ""
    let candidateString = existingString.stringByReplacingCharactersInRange(
        range.toStringRangeIn(existingString)!,
        withString: string
    )

    // Do validation...

    return true
}