Swift RangeからのNSRange?


175

問題: Rangeを使用するSwift Stringを使用しているときにNSAttributedStringがNSRangeを取得する

let text = "Long paragraph saying something goes here!"
let textRange = text.startIndex..<text.endIndex
let attributedString = NSMutableAttributedString(string: text)

text.enumerateSubstringsInRange(textRange, options: NSStringEnumerationOptions.ByWords, { (substring, substringRange, enclosingRange, stop) -> () in

    if (substring == "saying") {
        attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: substringRange)
    }
})

次のエラーを生成します。

エラー:「範囲」は「NSRange」に変換できませんattributedString.addAttribute(NSForegroundColorAttributeName、value:NSColor.redColor()、range:substringRange)



2
@Suhaibそれは逆になっています。
geoff

回答:


262

SwiftのString範囲とNSString範囲は「互換性がありません」。たとえば、😄のような絵文字は1つのSwift文字として数えられますが、2つのNSString 文字(いわゆるUTF-16サロゲートペア)として数えられます。

したがって、文字列にそのような文字が含まれている場合、提案されたソリューションは予期しない結果を生成します。例:

let text = "😄😄😄Long paragraph saying!"
let textRange = text.startIndex..<text.endIndex
let attributedString = NSMutableAttributedString(string: text)

text.enumerateSubstringsInRange(textRange, options: NSStringEnumerationOptions.ByWords, { (substring, substringRange, enclosingRange, stop) -> () in
    let start = distance(text.startIndex, substringRange.startIndex)
    let length = distance(substringRange.startIndex, substringRange.endIndex)
    let range = NSMakeRange(start, length)

    if (substring == "saying") {
        attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: range)
    }
})
println(attributedString)

出力:

😄😄😄ロングパラグラー{
} phは言う{
    NSColor = "NSCalibratedRGBColorSpace 1 0 0 1";
} ing!{
}

ご覧のとおり、「ph say」は「saying」ではなく属性でマークされています。

以来 NS(Mutable)AttributedString、最終的に必要となるNSStringNSRange、に与えられた文字列を変換するために、実際に優れているNSString最初の。次に、これsubstringRangeNSRange範囲であり、範囲を変換する必要はありません。

let text = "😄😄😄Long paragraph saying!"
let nsText = text as NSString
let textRange = NSMakeRange(0, nsText.length)
let attributedString = NSMutableAttributedString(string: nsText)

nsText.enumerateSubstringsInRange(textRange, options: NSStringEnumerationOptions.ByWords, { (substring, substringRange, enclosingRange, stop) -> () in

    if (substring == "saying") {
        attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: substringRange)
    }
})
println(attributedString)

出力:

😄😄😄長い段落{
}言う{
    NSColor = "NSCalibratedRGBColorSpace 1 0 0 1";
}!{
}

Swift 2の更新:

let text = "😄😄😄Long paragraph saying!"
let nsText = text as NSString
let textRange = NSMakeRange(0, nsText.length)
let attributedString = NSMutableAttributedString(string: text)

nsText.enumerateSubstringsInRange(textRange, options: .ByWords, usingBlock: {
    (substring, substringRange, _, _) in

    if (substring == "saying") {
        attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: substringRange)
    }
})
print(attributedString)

Swift 3の更新:

let text = "😄😄😄Long paragraph saying!"
let nsText = text as NSString
let textRange = NSMakeRange(0, nsText.length)
let attributedString = NSMutableAttributedString(string: text)

nsText.enumerateSubstrings(in: textRange, options: .byWords, using: {
    (substring, substringRange, _, _) in

    if (substring == "saying") {
        attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.red, range: substringRange)
    }
})
print(attributedString)

Swift 4の更新:

Swift 4(Xcode 9)以降、Swift標準ライブラリはRange<String.Index>との間の変換方法を提供しますNSRange。への変換NSStringは不要になりました。

let text = "😄😄😄Long paragraph saying!"
let attributedString = NSMutableAttributedString(string: text)

text.enumerateSubstrings(in: text.startIndex..<text.endIndex, options: .byWords) {
    (substring, substringRange, _, _) in
    if substring == "saying" {
        attributedString.addAttribute(.foregroundColor, value: NSColor.red,
                                      range: NSRange(substringRange, in: text))
    }
}
print(attributedString)

ここsubstringRangeRange<String.Index>、それは対応に変換されNSRange

NSRange(substringRange, in: text)

74
OSXで絵文字を入力したい人のために-Control-Command-スペースバーで文字ピッカーが表示されます
Jay

2
これは、複数の単語を照合する場合には機能せず、照合する文字列全体が何であるかわかりません。APIから文字列を取得し、それを別の文字列内で使用していて、APIからの文字列に下線を付けたい場合、サブ文字列がAPIからの文字列と他の文字列の両方に含まれないことを保証できませんストリング!何か案は?
simonthumper

NSMakeRange Changed str.substringWithRange(Range <String.Index>(start:str.startIndex、end:str.endIndex))// "こんにちは、遊び場"これは変更
HariKrishnan.P

(または)文字列をキャスト--- let substring =(string as NSString).substringWithRange(NSMakeRange(start、length))
HariKrishnan.P

2
あなたはそれに言及しRange<String.Index>NSString互換性がありません。それらの対応物にも互換性はありませんか?つまりNSRangeString互換性がありませんか?AppleのAPIの1つが具体的に2つを組み合わせているため
です。matches

56

あなたが説明したようなケースでは、私はこれがうまくいくことを発見しました。それは比較的短くて甘いです:

 let attributedString = NSMutableAttributedString(string: "follow the yellow brick road") //can essentially come from a textField.text as well (will need to unwrap though)
 let text = "follow the yellow brick road"
 let str = NSString(string: text) 
 let theRange = str.rangeOfString("yellow")
 attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.yellowColor(), range: theRange)

11
attributedString.addAttributeは迅速範囲では動作しません
Paludis

7
@Paludis、あなたは正しいですが、このソリューションはSwiftの範囲を使用しようとはしていません。を使用していNSRangeます。strNSStringなので、をstr.RangeOfString()返しますNSRange
tjpaul 2016

3
あなたはまたしてライン2と3を交換することにより、ライン2内の重複する文字列を削除することができますlet str = attributedString.string as NSString
ジェイソン・ムーア

2
これはローカリゼーションの悪夢です。
スルタン

29

答えは結構ですが、Swift 4を使用すると、コードを少し簡略化できます。

let text = "Test string"
let substring = "string"

let substringRange = text.range(of: substring)!
let nsRange = NSRange(substringRange, in: text)

range関数の結果はラップ解除する必要があるため、注意が必要です。


10

可能な解決策

Swiftは、NSRangeの作成に使用できる開始と終了の間の距離を測定するdistance()を提供します。

let text = "Long paragraph saying something goes here!"
let textRange = text.startIndex..<text.endIndex
let attributedString = NSMutableAttributedString(string: text)

text.enumerateSubstringsInRange(textRange, options: NSStringEnumerationOptions.ByWords, { (substring, substringRange, enclosingRange, stop) -> () in
    let start = distance(text.startIndex, substringRange.startIndex)
    let length = distance(substringRange.startIndex, substringRange.endIndex)
    let range = NSMakeRange(start, length)

//    println("word: \(substring) - \(d1) to \(d2)")

        if (substring == "saying") {
            attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: range)
        }
})

2
注:文字列に絵文字などの文字を使用すると、これが壊れる可能性があります-Martinの応答を参照してください。
Jay

7

私にとってこれは完璧に機能します:

let font = UIFont.systemFont(ofSize: 12, weight: .medium)
let text = "text"
let attString = NSMutableAttributedString(string: "exemple text :)")

attString.addAttributes([.font: font], range:(attString.string as NSString).range(of: text))

label.attributedText = attString

5

スウィフト4:

もちろん、Swift 4にはすでにNSRangeの拡張機能があることを知っています。

public init<R, S>(_ region: R, in target: S) where R : RangeExpression,
    S : StringProtocol, 
    R.Bound == String.Index, S.Index == String.Index

ほとんどの場合、この初期化で十分です。使い方を見る:

let string = "Many animals here: 🐶🦇🐱 !!!"

if let range = string.range(of: "🐶🦇🐱"){
     print((string as NSString).substring(with: NSRange(range, in: string))) //  "🐶🦇🐱"
 }

しかし、SwiftのStringインスタンスなしでRange <String.Index>からNSRangeに直接変換することができます。

代わりに、汎用の初期化あなたから必要と使用法対象としてパラメータ文字列を、あなたが持っていない場合、ターゲットの手で文字列を使用すると、直接変換を作成することができます

extension NSRange {
    public init(_ range:Range<String.Index>) {
        self.init(location: range.lowerBound.encodedOffset,
              length: range.upperBound.encodedOffset -
                      range.lowerBound.encodedOffset) }
    }

または、範囲自体に特化した拡張機能を作成できます

extension Range where Bound == String.Index {
    var nsRange:NSRange {
    return NSRange(location: self.lowerBound.encodedOffset,
                     length: self.upperBound.encodedOffset -
                             self.lowerBound.encodedOffset)
    }
}

使用法:

let string = "Many animals here: 🐶🦇🐱 !!!"
if let range = string.range(of: "🐶🦇🐱"){
    print((string as NSString).substring(with: NSRange(range))) //  "🐶🦇🐱"
}

または

if let nsrange = string.range(of: "🐶🦇🐱")?.nsRange{
    print((string as NSString).substring(with: nsrange)) //  "🐶🦇🐱"
}

スウィフト5:

Swift文字列のデフォルトでのUTF-8エンコーディングへの移行により、の使用はencodedOffset非推奨と見なされ、範囲を文字列自体のインスタンスなしでNSRangeに変換することはできません。オフセットを計算するには、 UTF-8でエンコードされ、オフセットを計算する前にUTF-16に変換する必要があります。したがって、現時点での最善のアプローチは、一般的なinitを使用することです。


の使用encodedOffset有害と見なされ非推奨となります。
Martin R

3

スウィフト4

二つの方法があると思います。

1. NSRange(範囲、in:)

2. NSRange(場所:、長さ:)

サンプルコード:

let attributedString = NSMutableAttributedString(string: "Sample Text 12345", attributes: [.font : UIFont.systemFont(ofSize: 15.0)])

// NSRange(range, in: )
if let range = attributedString.string.range(of: "Sample")  {
    attributedString.addAttribute(.foregroundColor, value: UIColor.orange, range: NSRange(range, in: attributedString.string))
}

// NSRange(location: , length: )
if let range = attributedString.string.range(of: "12345") {
    attributedString.addAttribute(.foregroundColor, value: UIColor.green, range: NSRange(location: range.lowerBound.encodedOffset, length: range.upperBound.encodedOffset - range.lowerBound.encodedOffset))
}

スクリーンショット: ここに画像の説明を入力してください


の使用encodedOffset有害と見なされ非推奨となります。
Martin R

1

既存の属性を保持するSwift 3 Extension Variant

extension UILabel {
  func setLineHeight(lineHeight: CGFloat) {
    guard self.text != nil && self.attributedText != nil else { return }
    var attributedString = NSMutableAttributedString()

    if let attributedText = self.attributedText {
      attributedString = NSMutableAttributedString(attributedString: attributedText)
    } else if let text = self.text {
      attributedString = NSMutableAttributedString(string: text)
    }

    let style = NSMutableParagraphStyle()
    style.lineSpacing = lineHeight
    style.alignment = self.textAlignment
    let str = NSString(string: attributedString.string)

    attributedString.addAttribute(NSParagraphStyleAttributeName,
                                  value: style,
                                  range: str.range(of: str as String))
    self.attributedText = attributedString
  }
}

0
func formatAttributedStringWithHighlights(text: String, highlightedSubString: String?, formattingAttributes: [String: AnyObject]) -> NSAttributedString {
    let mutableString = NSMutableAttributedString(string: text)

    let text = text as NSString         // convert to NSString be we need NSRange
    if let highlightedSubString = highlightedSubString {
        let highlightedSubStringRange = text.rangeOfString(highlightedSubString) // find first occurence
        if highlightedSubStringRange.length > 0 {       // check for not found
            mutableString.setAttributes(formattingAttributes, range: highlightedSubStringRange)
        }
    }

    return mutableString
}

0

私はSwift言語が大好きですが、互換性のないNSAttributedStringSwift Rangeを使用するとNSRange、頭が痛くなりすぎます。したがって、すべてのゴミを回避するためにNSMutableAttributedString、強調表示された単語を色で設定してを返す次のメソッドを考案しました。

これは絵文字では機能しませ。必要に応じて変更します。

extension String {
    func getRanges(of string: String) -> [NSRange] {
        var ranges:[NSRange] = []
        if contains(string) {
            let words = self.components(separatedBy: " ")
            var position:Int = 0
            for word in words {
                if word.lowercased() == string.lowercased() {
                    let startIndex = position
                    let endIndex = word.characters.count
                    let range = NSMakeRange(startIndex, endIndex)
                    ranges.append(range)
                }
                position += (word.characters.count + 1) // +1 for space
            }
        }
        return ranges
    }
    func highlight(_ words: [String], this color: UIColor) -> NSMutableAttributedString {
        let attributedString = NSMutableAttributedString(string: self)
        for word in words {
            let ranges = getRanges(of: word)
            for range in ranges {
                attributedString.addAttributes([NSForegroundColorAttributeName: color], range: range)
            }
        }
        return attributedString
    }
}

使用法:

// The strings you're interested in
let string = "The dog ran after the cat"
let words = ["the", "ran"]

// Highlight words and get back attributed string
let attributedString = string.highlight(words, this: .yellow)

// Set attributed string
label.attributedText = attributedString

-3
let text:String = "Hello Friend"

let searchRange:NSRange = NSRange(location:0,length: text.characters.count)

let range:Range`<Int`> = Range`<Int`>.init(start: searchRange.location, end: searchRange.length)

6
あなたの答えを少し説明して、できればコードを適切にフォーマットしてみませんか?
SamB
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.