NSRange到Range <string.index> </string.index>

时间:2014-08-05 12:01:22

标签: nsstring swift ios8 nsrange

如何在Swift中将NSRange转换为Range<String.Index>

我想使用以下UITextFieldDelegate方法:

    func textField(textField: UITextField!,
        shouldChangeCharactersInRange range: NSRange,
        replacementString string: String!) -> Bool {

textField.text.stringByReplacingCharactersInRange(???, withString: string)

enter image description here

15 个答案:

答案 0 :(得分:305)

截至 Swift 4 (Xcode 9),Swift标准 library提供了在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> (包括所有Emojis案例,区域指标或其他扩展案例) 字形集群)没有中间转换为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
    }
}

此方法返回可选字符串范围,因为并非所有NSRange 对于给定的Swift字符串有效。

然后可以将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的更新:

已经提供了rangeFromNSRange()的Swift 2版本 作者:Serhii Yakovenko在this answer,我将其包括在内 这里是为了完整性:

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

NSRangeFromRange()的Swift 2版本是

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

答案 1 :(得分:258)

NSString的{​​{1}}版本(而不是Swift字符串)接受replacingCharacters(in: NSRange, with: NSString),因此一个简单的解决方案就是NSRange转换为{{1}首先。代理和替换方法名称在Swift 3和2中略有不同,因此取决于您使用的Swift:

Swift 3.0

String

Swift 2.x

NSString

答案 2 :(得分:18)

您需要使用Range<String.Index>代替经典NSRange。我这样做的方式(也许还有更好的方法)是将字符串&#39; 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)

答案 3 :(得分:17)

Martin R的

This answer似乎是正确的,因为它占据了Unicode。

然而,在帖子(Swift 1)时,他的代码并没有在Swift 2.0(Xcode 7)中编译,因为他们删除了var zoom = d3.behavior.zoom().scaleExtent([1, 10]).on("zoom", zoomed); var links = graph.links; var nodes = graph.nodes; svg.call(zoom); svg.append("svg:defs").selectAll("marker") .data(["publishedBy", "publishedOn", "depicts","postedOn","childOf"]) .enter().append("svg:marker") .attr("id", String) .attr("viewBox", "0 -5 10 10") .attr("refX", 15) .attr("refY", -1.5) .attr("markerWidth", 6) .attr("markerHeight", 6) .attr("orient", "auto") .append("svg:path") .attr("d", "M0,-5L10,0L0,5"); links.sort(function(a,b) { if (a.source > b.source) {return 1;} else if (a.source < b.source) {return -1;} else { if (a.target > b.target) {return 1;} if (a.target < b.target) {return -1;} else {return 0;} } }); for (var i=0; i<links.length; i++) { if (i != 0 && links[i].source == links[i-1].source && links[i].target == links[i-1].target) { links[i].linknum = links[i-1].linknum + 1; } else {links[i].linknum = 1;}; }; var link = svg.selectAll(".link") .data(links).enter().append("g") .attr("class", "link") .append("line") .attr("class", "link-line") .style("stroke-width", 1); var drag = d3.behavior.drag() .origin(function(d) { return d; }) .on("dragstart", dragstarted) .on("drag", dragged) .on("dragend", dragended); var node = svg.selectAll(".node") .data(graph.nodes).enter() .append("circle") .attr("class", "node") .attr("r", 20) .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }) .style("fill", function (d) { return color(d.type); }) .on("mouseover", mouseover) .on("mouseout", mouseout) .call(drag); 函数。更新版本如下:

Swift 2

advance()

Swift 3

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 4

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

答案 4 :(得分:7)

这与Emilie的答案类似,但是由于您具体询问如何将NSRange转换为Range<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) 
     ...

}

答案 5 :(得分:6)

@Emilie给出了很好的答案,而不是替换/竞争的答案 (Xcode6-β5)

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

请注意,索引占多个代码单元。标志(REGIONAL INDICATOR SYMBOL LETTERS ES)是8个字节,(FACE WITH TEARS OF JOY)是4个字节。 (在这种特殊情况下,事实证明UTF-8,UTF-16和UTF-32表示的字节数相同。)

将它包装在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

答案 6 :(得分:4)

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

       let strString = ((textField.text)! as NSString).stringByReplacingCharactersInRange(range, withString: string)

 }

答案 7 :(得分:2)

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

答案 8 :(得分:1)

这是我的最大努力。但是这不能检查或检测错误的输入参数。

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 代码单元。来自Swift Range<String.Index>的{​​{1}}是 opaque 相对类型,它仅提供相等和导航操作。这是故意隐藏的设计。

虽然String似乎映射到UTF-16代码单元偏移量,但这只是一个实现细节,我找不到任何关于任何保证的提及。这意味着可以随时更改实施细节。 Swift Range<String.Index>的内部表示没有明确定义,我不能依赖它。

String值可以直接映射到NSRange索引。但是没有办法将其转换为String.UTF16View

Swift String.Index是迭代Swift String.Index的索引,它是 Unicode字形集群。然后,您必须提供正确的Character选择正确的字形集群。如果您提供的错误范围与上述示例类似,则会产生错误的结果,因为无法计算出正确的字形集群范围。

如果保证NSRange UTF-16代码单位偏移,那么问题就变得简单了。但它不可能发生。

逆转换

无论如何,逆转换可以精确地完成。

String.Index

结果。

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

答案 9 :(得分:1)

我发现最干净的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
}

答案 10 :(得分:0)

Swift 3.0 beta官方文档在 UTF16View Elements匹配NSString字符标题

中的标题 String.UTF16View 下为此情况提供了标准解决方案

答案 11 :(得分:0)

在接受的答案中,我发现选项很麻烦。这适用于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
}

答案 12 :(得分:0)

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

答案 13 :(得分:0)

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    
    guard let current = textField.text, let r = Range(range, in: current) else {
        return false
    }
    
    let text = current.replacingCharacters(in: r, with: string)
    // ...
    return true
}

答案 14 :(得分:0)

Swift 5 解决方案

带有主要扩展的简短回答

#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>
#define DHTPIN 2
#define DHTTYPE 22

DHT dht(DHTPIN, DHTTYPE);


int buttonState1 = 0;
const int But2 = 11; 
 
int buttonState = 0; 
const int But1 = 12; 

byte termometru[8] = {B00100, B01010, B01010, B01110, B01110, B11111, B11111, B01110}; //chart for temperatura

byte picatura[8] = {B00100, B00100, B01010, B01010, B10001, B10001, B10001, B01110,}; //char for humidity

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); //lcd address
 
void setup()
{
                        // this value is obtained by calibrating the scale with known weights;
    dht.begin();
                    // reset the scale to 0
    lcd.begin(20,2);
    lcd.backlight();
    lcd.clear(); 
    lcd.createChar(1,termometru);
    lcd.createChar(2,picatura);
    pinMode(But1, INPUT);
    pinMode(But2, INPUT);
    Serial.begin(19200); 
 }

void display()
{

  lcd.setCursor(0, 1);
  lcd.write(1); 
  lcd.setCursor(2,1); 
  lcd.print(dht.readTemperature(0, 1)); 
  lcd.setCursor(6, 1);
  lcd.print(" ");
  lcd.setCursor(7, 1);
  lcd.print((char)223); 
  lcd.print("C"); 
   
  lcd.setCursor(10, 1);
  lcd.write(2); 
  lcd.setCursor(12, 1);
  lcd.print(dht.readHumidity(1)); 
  lcd.setCursor(14, 1);
  lcd.print(" "); 
  lcd.setCursor(15,1);
  lcd.print("%"); 
 } 
 
void buton()
{
buttonState = digitalRead(But1);
     delay(10); 
    if (buttonState == HIGH) 
        { lcd.backlight(); }
        else {
        lcd.noBacklight(); 
        }}


void loop() 
{
    int chk = dht.read(DHTPIN);
    display(); 
    buton();
    
    Serial.print("T: ");
    Serial.println(dht.readTemperature());
    Serial.print("H: ");
    Serial.println(dht.readHumidity());
    Serial.print("W: ");
  
}

对于detailed answer check here