2017-07-31 99 views
3

我试图添加一个初始化程序到RangeSwift结构扩展添加初始化程序

import Foundation 

extension Range { 
    init(_ range: NSRange, in string: String) { 
     let lower = string.index(string.startIndex, offsetBy: range.location) 
     let upper = string.index(string.startIndex, offsetBy: NSMaxRange(range)) 
     self.init(uncheckedBounds: (lower: lower, upper: upper)) 
    } 
} 

但是,最后一行有一个Swift编译器错误。

无法将类型的值 '(低级:String.Index,上部:String.Index)'(又名 '(低级:String.CharacterView.Index,上部:String.CharacterView.Index)'),以预期参数类型'(lower:_,upper:_)'

如何获得它来编译?

+1

相关:[NSRange到范围](https://stackoverflow.com/questions/25138339/nsrange-to-rangestring-index) – ma11hew28

回答

1

的问题是,即使String.Index确实符合Comparable协议,你还需要指定要与public struct Range<Bound> where Bound : Comparable {}

注意上班范围类型:作为NSString使用UTF-16,检查this和同样在您提到的link中,您的初始代码对于包含多个UTF-16代码点的字符无法正常工作。以下是更新工作版本的雨燕3.

extension Range where Bound == String.Index { 
    init(_ range: NSRange, in string: String) { 
     let lower16 = string.utf16.index(string.utf16.startIndex, offsetBy: range.location) 
     let upper16 = string.utf16.index(string.utf16.startIndex, offsetBy: NSMaxRange(range)) 

     if let lower = lower16.samePosition(in: string), 
      let upper = upper16.samePosition(in: string) { 
      self.init(lower..<upper) 
     } else { 
      fatalError("init(range:in:) could not be implemented") 
     } 
    } 
} 

let string = "❄️Let it snow! ☃️" 

let range1 = NSRange(location: 0, length: 1) 
let r1 = Range<String.Index>(range1, in: string) // ❄️ 

let range2 = NSRange(location: 1, length: 2) 
let r2 = Range<String.Index>(range2, in: string) // fatal error: init(range:in:) could not be implemented 

要回答OP的评论:问题是一个NSString对象编码兼容Unicode的文本字符串,表示为一个序列UTF-16编码单元。构成字符串内容的Unicode标量值可以长达21位。较长的标量值可能需要两个UInt16值进行存储。

因此,像❄️这样的字母在NSString中占用了两个UInt16值,但在String中只有一个。当你将一个NSRange参数传递给初始值设定项时,你可能会期望它在NSString中正常工作。

在我的例子,对于r1r2结果后,你转换到string UTF16是“❄️”和一个致命的错误。同时,您最初解决方案的结果分别是'❄️L'和'Le'。希望你看到不同之处。

如果你坚持使用解决方案而不转换为utf16,你可以看看the Swift source code做出决定。在Swift 4中,你将初始化器作为一个内置的lib。代码如下。

extension Range where Bound == String.Index { 
    public init?(_ range: NSRange, in string: String) { 
    let u = string.utf16 
    guard range.location != NSNotFound, 
     let start = u.index(u.startIndex, offsetBy: range.location, limitedBy: u.endIndex), 
     let end = u.index(u.startIndex, offsetBy: range.location + range.length, limitedBy: u.endIndex), 
     let lowerBound = String.Index(start, within: string), 
     let upperBound = String.Index(end, within: string) 
    else { return nil } 

    self = lowerBound..<upperBound 
    } 
} 
+0

我不明白你为什么需要使用'string'的UTF-16编码。我的初始代码似乎可以正常工作,即使对于带有emojis的字符串也是如此。请提供一个反例。 – ma11hew28

+0

这将是一个非常长的评论来回答你的问题,所以我决定更新我的答案。我也编辑了一下我的例子,让差异更加明显。 – Lawliet

+0

嗯,当然,我看到了区别,但我仍不明白为什么您的示例中的结果是正确的。我想我会拿苹果公司的话来说。谢谢! :-) – ma11hew28

0

这个方法的签名要求“绑定”型(至少在迅速4)

由于绑定只是一个的“可比”相关联的类型和String.Index符合它,你应该只是能够施展它。

extension Range { 
    init(_ range: NSRange, in string: String) { 
     let lower : Bound = string.index(string.startIndex, offsetBy: range.location) as! Bound 
     let upper : Bound = string.index(string.startIndex, offsetBy: NSMaxRange(range)) as! Bound 

     self.init(uncheckedBounds: (lower: lower, upper: upper)) 
    } 
} 

https://developer.apple.com/documentation/swift/rangeexpression/2894257-bound

1

需要约束的范围内初始化到绑定等于字符串。指数,让你的NSRange UTF16索引和查找您的字符串的字符串索引的相同位置如下:

extension Range where Bound == String.Index { 
    init?(_ range: NSRange, in string: String) { 
     guard 
      let start = string.utf16.index(string.utf16.startIndex, offsetBy: range.location, limitedBy: string.utf16.endIndex), 
      let end = string.utf16.index(string.utf16.startIndex, offsetBy: range.location + range.length, limitedBy: string.utf16.endIndex), 
      let startIndex = start.samePosition(in: string), 
      let endIndex = end.samePosition(in: string) 
     else { 
      return nil 
     } 
     self = startIndex..<endIndex 
    } 
} 
+1

感谢您的注意。我已经更新了我的答案。 – Lawliet

+0

@Lawliet我欢迎 –