[英]NSRange from Swift Range?
Problem: NSAttributedString takes an NSRange while I'm using a Swift String that uses Range问题:当我使用使用 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)
}
})
Produces the following error:产生以下错误:
error: 'Range' is not convertible to 'NSRange' attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: substringRange)
错误:'Range' 不能转换为 'NSRange'attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: substringRange)
Swift String
ranges and NSString
ranges are not "compatible". Swift
String
范围和NSString
范围不“兼容”。 For example, an emoji like 😄 counts as one Swift character, but as two NSString
characters (a so-called UTF-16 surrogate pair).例如,像 😄 这样的表情符号算作一个 Swift 字符,但算作两个
NSString
字符(所谓的 UTF-16 代理对)。
Therefore your suggested solution will produce unexpected results if the string contains such characters.因此,如果字符串包含此类字符,您建议的解决方案将产生意外结果。 Example:
例子:
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)
Output:输出:
😄😄😄Long paragra{ }ph say{ NSColor = "NSCalibratedRGBColorSpace 1 0 0 1"; }ing!{ }
As you see, "ph say" has been marked with the attribute, not "saying".如您所见,“ph say”已被标记为属性,而不是“saying”。
Since NS(Mutable)AttributedString
ultimately requires an NSString
and an NSRange
, it is actually better to convert the given string to NSString
first.由于
NS(Mutable)AttributedString
最终需要一个NSString
和一个NSRange
,实际上最好先将给定的字符串转换为NSString
。 Then the substringRange
is an NSRange
and you don't have to convert the ranges anymore:然后
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)
Output:输出:
😄😄😄Long paragraph { }saying{ NSColor = "NSCalibratedRGBColorSpace 1 0 0 1"; }!{ }
Update for Swift 2: 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)
Update for Swift 3: 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)
Update for Swift 4: Swift 4 更新:
As of Swift 4 (Xcode 9), the Swift standard library provides method to convert between Range<String.Index>
and NSRange
.从 Swift 4 (Xcode 9) 开始,Swift 标准库提供了在
Range<String.Index>
和NSRange
之间进行转换的方法。 Converting to NSString
is no longer necessary:不再需要转换为
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)
Here substringRange
is a Range<String.Index>
, and that is converted to the corresponding NSRange
with这里
substringRange
是一个Range<String.Index>
,它被转换为相应的NSRange
NSRange(substringRange, in: text)
For cases like the one you described, I found this to work.对于您所描述的情况,我发现这是可行的。 It's relatively short and sweet:
它相对简短而甜蜜:
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)
The answers are fine, but with Swift 4 you could simplify your code a bit:答案很好,但是使用 Swift 4,您可以稍微简化一下代码:
let text = "Test string"
let substring = "string"
let substringRange = text.range(of: substring)!
let nsRange = NSRange(substringRange, in: text)
Be cautious, as the result of range
function has to be unwrapped.小心,因为必须解开
range
函数的结果。
Possible Solution可能的解决方案
Swift provides distance() which measures the distance between start and end that can be used to create an NSRange: 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)
}
})
For me this works perfectly:对我来说,这非常有效:
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 4:斯威夫特 4:
Sure, I know that Swift 4 has an extension for NSRange already当然,我知道 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
I know in most cases this init is enough.我知道在大多数情况下这个 init 就足够了。 See its usage:
查看它的用法:
let string = "Many animals here: 🐶🦇🐱 !!!"
if let range = string.range(of: "🐶🦇🐱"){
print((string as NSString).substring(with: NSRange(range, in: string))) // "🐶🦇🐱"
}
But conversion can be done directly from Range< String.Index > to NSRange without Swift's String instance.但是可以直接从 Range<String.Index> 转换为 NSRange,无需 Swift 的 String 实例。
Instead of generic init usage which requires from you the target parameter as String and if you don't have target string at hand you can create conversion directly而不是通用init用法,它需要您将目标参数作为字符串,如果您手头没有目标字符串,您可以直接创建转换
extension NSRange {
public init(_ range:Range<String.Index>) {
self.init(location: range.lowerBound.encodedOffset,
length: range.upperBound.encodedOffset -
range.lowerBound.encodedOffset) }
}
or you can create the specialized extension for Range itself或者您可以为 Range 本身创建专门的扩展
extension Range where Bound == String.Index {
var nsRange:NSRange {
return NSRange(location: self.lowerBound.encodedOffset,
length: self.upperBound.encodedOffset -
self.lowerBound.encodedOffset)
}
}
Usage:用法:
let string = "Many animals here: 🐶🦇🐱 !!!"
if let range = string.range(of: "🐶🦇🐱"){
print((string as NSString).substring(with: NSRange(range))) // "🐶🦇🐱"
}
or要么
if let nsrange = string.range(of: "🐶🦇🐱")?.nsRange{
print((string as NSString).substring(with: nsrange)) // "🐶🦇🐱"
}
Swift 5:斯威夫特 5:
Due to the migration of Swift strings to UTF-8 encoding by default, the usage of encodedOffset
is considered as deprecated and Range cannot be converted to NSRange without an instance of String itself, because in order to calculate the offset we need the source string which is encoded in UTF-8 and it should be converted to UTF-16 before calculating offset.由于 Swift 字符串默认迁移到 UTF-8 编码,因此使用
encodedOffset
被认为是不推荐使用的,并且 Range 在没有 String 本身的实例的情况下无法转换为 NSRange,因为为了计算偏移量我们需要源字符串以 UTF-8 编码,在计算偏移量之前应将其转换为 UTF-16。 So best approach, for now, is to use generic init .所以目前最好的方法是使用通用的init 。
Swift 4斯威夫特 4
I think, there are two ways.我想,有两种方法。
1. NSRange(range, in: ) 1. NSRange(范围,在:)
2. NSRange(location:, length: ) 2. NSRange(位置:,长度:)
Sample code:示例代码:
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 Variant that preserves existing attributes.保留现有属性的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
}
}
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
}
I love the Swift language, but using NSAttributedString
with a Swift Range
that is not compatible with NSRange
has made my head hurt for too long.我喜欢 Swift 语言,但是将
NSAttributedString
与与NSRange
不兼容的 Swift Range
一起NSRange
让我的NSRange
了太久。 So to get around all that garbage I devised the following methods to return an NSMutableAttributedString
with the highlighted words set with your color.因此,为了解决所有这些垃圾,我设计了以下方法来返回一个
NSMutableAttributedString
,其中突出显示的单词设置为您的颜色。
This does not work for emojis.这并不适用于表情符号工作。 Modify if you must.
如果必须,请修改。
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
}
}
Usage:用法:
// 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
My solution is a string extension that first gets the swift range then get's the distance from the start of the string to the start and end of the substring.我的解决方案是一个字符串扩展,它首先获取 swift 范围,然后获取从字符串开头到子字符串开头和结尾的距离。
These values are then used to calculate the start and length of the substring.然后使用这些值来计算子字符串的开始和长度。 We can then apply these values to the NSMakeRange constructor.
然后我们可以将这些值应用到 NSMakeRange 构造函数。
This solution works with substrings that consist of multiple words, which a lot of the solutions here using enumerateSubstrings let me down on.此解决方案适用于由多个单词组成的子字符串,这里使用 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)
}
}
Swift 5 Solution Swift 5 解决方案
Converting Range into NSRange将范围转换为 NSRange
As the 'encodedOffset' is deprecated, so now in order to convert String.Index to Int we need the reference of original string from which Range<String.Index> was derived.
由于不推荐使用'encodedOffset' ,所以现在为了将String.Index转换为Int,我们需要从其派生Range<String.Index>的原始字符串的引用。
A convenient detailed extension for NSRange could be as below: 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)
}
}
While the shorthand extension is as below虽然简写扩展如下
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))
}
}
Now we can use any Range to convert it into NSRange as below, sharing my own requirement which led me to write above extensions现在我们可以使用任何 Range 将其转换为 NSRange,如下所示,分享我自己的需求,这导致我编写了上述扩展
I was using below String extension for finding all the ranges of specific word from the 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
}
}
My requirement was to change the colour of specific words in a String, so for that I wrote this extension which does the job我的要求是更改字符串中特定单词的颜色,因此我编写了这个扩展程序来完成这项工作
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
}
}
Finally I was using it from my main code as below最后我从我的主要代码中使用它,如下所示
let text = "Collected".localized() + " + " + "Cancelled".localized() + " + " + "Pending".localized()
myLabel.attributedText = NSAttributedString.colored(originalText: text,
wordToColor: "+",
currentColor: UIColor.purple,
differentColor: UIColor.blue)
And the result is as below, having the colour of + sign changed as blue from the main text colour which is purple.结果如下,+号的颜色从紫色的正文颜色变为蓝色。
Hope this helps someone in need.希望这可以帮助有需要的人。 Thanks!
谢谢!
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)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.