問題: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)
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
は最終的にNSString
とNSRange
を必要とするので、与えられた文字列を最初にNSString
に変換するのが実際には良い方法です。 substringRange
はNSRange
であり、範囲を変換する必要はもうありません。
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)
ここでsubstringRange
はRange<String.Index>
であり、それは対応するNSRange
に変換されます。
NSRange(substringRange, in: text)
あなたが説明したようなケースでは、私はこれがうまくいくことがわかりました。それは比較的短くて甘いです。
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)
答えは大丈夫ですが、Swift 4ではコードを少し単純化することができます。
let text = "Test string"
let substring = "string"
let substringRange = text.range(of: substring)!
let nsRange = NSRange(substringRange, in: text)
range
関数の結果はラップを解除する必要があるため、注意が必要です。
可能な解決策
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)
}
})
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を使うことです。
私にとってはこれは完璧に動作します。
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
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
}
}
私はSwift言語が大好きですが、NSAttributedString
と互換性のないSwift 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
スイフト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))
}
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
}