web-dev-qa-db-ja.com

スイフトレンジからNSRange?

問題: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)
    }
})

次のエラーが発生します。

エラー: 'Range'は 'NSRange'に変換できませんattributedString.addAttribute(NSForegroundColorAttributeName、値:NSColor.redColor()、範囲:substringRange)

140
Jay

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 {{。。。。。。 ing!{
} 

お分かりのように、「ph say」は「say」ではなく属性でマークされています。

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)
224
Martin R

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

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

答えは大丈夫ですが、Swift 4ではコードを少し単純化することができます。

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

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

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

13

可能な解決策

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

Swift 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

ほとんどの場合、このinitで十分です。使い方を見てください。

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へ直接変換することができます。

一般的なinitの代わりに、targetパラメータをStringとして使用する必要があります。あなたが手元にターゲット文字列を持っていないなら、あなたは直接変換を作成することができます

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

あるいはRange自身のために特別な拡張子を作成することができます

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)) //  "????????????"
}

Swift 5:

デフォルトではSwift文字列がUTF-8エンコーディングに移行されているため、encodedOffsetの使用は廃止予定と見なされ、String自体のインスタンスがないとRangeをNSRangeに変換できませんはUTF-8でエンコードされており、オフセットを計算する前にUTF-16に変換する必要があります。そのため、今のところ最善のアプローチはgenericinitを使うことです。

6
Dmitry A.

私にとってはこれは完璧に動作します。

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
4
Breno Vinícios

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

私はSwift言語が大好きですが、NSAttributedStringと互換性のないSwift RangeNSRangeを一緒に使うと、頭が痛くなり過ぎてしまいました。そのため、これらのゴミをすべて回避するために、強調表示された単語をあなたの色で設定した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
0
Brandon A

スイフト4

2つの方法があります。

1。 NSRange(範囲、内:)

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

スクリーンショット: enter image description here

0
Den
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
orkoden