2017-07-31 31 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我歡迎 –