NSRange от Swift Range?
Проблема: NSAttributedString принимает 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)
}
})
Выдает следующую ошибку:
ошибка: "Диапазон" не может быть преобразован в "NSRange" attribute.String.addAttribute (NSForegroundColorAttributeName, значение: NSColor.redColor(), диапазон: substringRange)
13 ответов
Стриж 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)
Выход:
Длинная парагра { }ph сказать { NSColor = "NSCalibratedRGBColorSpace 1 0 0 1"; } ИНГ!{ }
Как видите, "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 предоставляет distance(), который измеряет расстояние между началом и концом, которое можно использовать для создания 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)
}
})
Для меня это прекрасно работает:
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 5
Преобразование диапазона в NSRange
Поскольку 'encodedOffset' устарел, теперь для преобразования String.Index в Int нам нужна ссылка на исходную строку, из которой был получен Range<String.Index> .
Удобное подробное расширение для NSRange может выглядеть следующим образом:
extension NSRange {
public init(range: Range<String.Index>,
originalText: String) {
let range_LowerBound_INDEX = range.lowerBound
let range_UpperBound_INDEX = range.upperBound
let range_LowerBound_INT = range_LowerBound_INDEX.utf16Offset(in: originalText)
let range_UpperBound_INT = range_UpperBound_INDEX.utf16Offset(in: originalText)
let locationTemp = range_LowerBound_INT
let lengthTemp = range_UpperBound_INT - range_LowerBound_INT
self.init(location: locationTemp,
length: lengthTemp)
}
}
В то время как сокращенное расширение выглядит следующим образом
extension NSRange {
public init(range: Range<String.Index>,
originalText: String) {
self.init(location: range.lowerBound.utf16Offset(in: originalText),
length: range.upperBound.utf16Offset(in: originalText) - range.lowerBound.utf16Offset(in: originalText))
}
}
Теперь мы можем использовать любой диапазон, чтобы преобразовать его в NSRange, как показано ниже, разделяя мои собственные требования, которые заставили меня написать вышеупомянутые расширения.
Я использовал расширение String ниже для поиска всех диапазонов конкретного слова из String
extension String {
func ranges(of substring: String, options: CompareOptions = [], locale: Locale? = nil) -> [Range<Index>] {
var ranges: [Range<Index>] = []
while let range = range(of: substring, options: options, range: (ranges.last?.upperBound ?? self.startIndex)..<self.endIndex, locale: locale) {
ranges.append(range)
}
return ranges
}
}
Мое требование состояло в том, чтобы изменить цвет определенных слов в строке, поэтому для этого я написал это расширение, которое выполняет эту работу
extension NSAttributedString {
static func colored(originalText:String,
wordToColor:String,
currentColor:UIColor,
differentColor:UIColor) -> NSAttributedString {
let attr = NSMutableAttributedString(string: originalText)
attr.beginEditing()
attr.addAttribute(NSAttributedString.Key.foregroundColor,
value: currentColor,
range: NSRange(location: 0, length: originalText.count))
// FOR COVERING ALL THE OCCURENCES
for eachRange in originalText.ranges(of: wordToColor) {
attr.addAttribute(NSAttributedString.Key.foregroundColor,
value: differentColor,
range: NSRange(range: eachRange, originalText: originalText))
}
attr.endEditing()
return attr
}
}
Наконец, я использовал его из своего основного кода, как показано ниже.
let text = "Collected".localized() + " + " + "Cancelled".localized() + " + " + "Pending".localized()
myLabel.attributedText = NSAttributedString.colored(originalText: text,
wordToColor: "+",
currentColor: UIColor.purple,
differentColor: UIColor.blue)
И результат будет таким, как показано ниже, когда цвет знака + изменился на синий по сравнению с основным цветом текста, который является фиолетовым.
Надеюсь, это поможет кому-то в нужде. Спасибо!
Свифт 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))) // ""
}
Но преобразование может быть выполнено непосредственно из Range
Вместо общего использования init, которое требует от вас целевого параметра как 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 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))
}
Вариант расширения Swift 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
}
}
Мое решение - расширение строки, которое сначала получает быстрый диапазон, а затем расстояние от начала строки до начала и конца подстроки.
Эти значения затем используются для вычисления начала и длины подстроки. Затем мы можем применить эти значения к конструктору NSMakeRange.
Это решение работает с подстроками, состоящими из нескольких слов, и многие решения здесь, использующие enumerateSubstrings, меня подводят.
extension String {
func NSRange(of substring: String) -> NSRange? {
// Get the swift range
guard let range = range(of: substring) else { return nil }
// Get the distance to the start of the substring
let start = distance(from: startIndex, to: range.lowerBound) as Int
//Get the distance to the end of the substring
let end = distance(from: startIndex, to: range.upperBound) as Int
//length = endOfSubstring - startOfSubstring
//start = startOfSubstring
return NSMakeRange(start, end - start)
}
}
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
}
Я люблю язык Swift, но использую NSAttributedString
со стремительным 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
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)