2014-11-20 3 views
98

Проблема: NSAttributedString принимает NSRange в то время как я использую Swift строку, которая использует диапазонNSRange от Swift Range?

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

Выдает следующую ошибку:

error: 'Range' is not convertible to 'NSRange' attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: substringRange)

+3

Возможный дубликат [NSRange ранжировать ] (http://stackoverflow.com/questions/25138339/nsrange-to-rangestring -index) – Suhaib

ответ

154

Swift String диапазонов и NSString диапазонов не "совместимы". Например, подобный эмози считается одним символом Swift, но как два символа 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) 

Выход:

 
Long paragra{ 
}ph say{ 
    NSColor = "NSCalibratedRGBColorSpace 1 0 0 1"; 
}ing!{ 
} 

Как вы видите, "говорят фот", который был отмечен атрибутом, а не "говорить".

Поскольку 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) 

Выход:

 
Long paragraph { 
}saying{ 
    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:

В Свифта 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) 
+47

Для тех, кто хочет набирать символы emoji на OSX - панель Control-Command-space вызывает набор символов – Jay

+0

Это не работает, если я сопоставляю несколько слов, и я не уверен, что соответствует всей строке является. Скажем, я возвращаю строку из API и использую ее в другой строке, и я хочу, чтобы строка из API была подчеркнута, я не могу гарантировать, что подстроки не будут находиться в строке из API, а другая строка! Есть идеи? – simonthumper

+0

NSMakeRange Изменено str.substringWithRange (Range (начало: str.startIndex, end: str.endIndex)) // «Привет, игровая площадка» это изменения –

9

Возможное решение

Swift предоставляет расстояние(), которое измеряет расстояние между началом и концом, что ca п быть использовано для создания 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 
    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

Примечание: это может сломаться при использовании символов, подобных emoji в строке. См. ответ Мартина. – Jay

+0

Да, мне нравится это решение. Он работает для простого текста. – wzhang84

34

Для случаев, как тот, который вы описали, я нашел это работать. Это относительно коротким и сладким:

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

attribitedString.addAttribute не будет работать с быстрым диапазоном – Paludis

+4

@Paludis, вы правы, но это решение не пытается использовать диапазон Swift. Он использует 'NSRange'. 'str' является' NSString', и поэтому 'str.RangeOfString()' возвращает 'NSRange'. – tjpaul

+2

Вы также можете удалить повторяющуюся строку в строке 2, заменив строки 2 и 3: 'let str = attributedString.string как NSString' –

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 
} 
-2
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) 
+3

Как объяснить немного ответа и, желательно, правильно форматировать код? – SamB

0

Я люблю Swift язык, но с использованием NSAttributedString с Swift Range, который не совместим с NSRange сделал меня разболелась голова слишком долго. Поэтому, чтобы обойти весь этот мусор, я разработал следующие методы, чтобы вернуть NSMutableAttributedString с выделенными словами, заданными вашим цветом.

Это не работа для emojis. Измените, если нужно.

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 
2

Ответы хорошо, но с Swift 4 вы можете упростить ваш код немного:

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

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

Be предостережений, как результат range функции имеет для развертывания.

2

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

Но преобразование может быть сделано непосредственно из диапазона < 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)) // "" 
} 
0

Swift 4

Я думаю, есть два пути.

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

Swif t 3 Вариант расширения, который сохраняет существующие атрибуты.

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

Для меня это отлично работает:

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 
Смежные вопросы