ios 来自 Swift Range 的 NSRange?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/27040924/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
NSRange from Swift Range?
提问by Jay
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' attributesString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: substringRange)
回答by Martin R
Swift String
ranges and NSString
ranges are not "compatible".
For example, an emoji like counts as one Swift character, but as two NSString
characters (a so-called UTF-16 surrogate pair).
SwiftString
范围和NSString
范围不“兼容”。例如,像这样的表情符号算作一个 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. Then the substringRange
is an NSRange
and you don't have to convert the ranges anymore:
由于NS(Mutable)AttributedString
最终需要 anNSString
和 an NSRange
,实际上最好将给定的字符串转换为NSString
first 。然后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
.
Converting to NSString
is no longer necessary:
从 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)
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)
回答by royherma
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)
回答by George Maisuradze
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
函数的结果必须被解包。
回答by Jay
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)
}
})
回答by Breno Vinícios
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
回答by Dmitry A.
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. See its usage:
我知道在大多数情况下这个 init 就足够了。查看它的用法:
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 initusage which requires from you the targetparameter as Stringand if you don't have targetstring 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. So best approach, for now, is to use generic init.
由于 Swift 字符串默认迁移到 UTF-8 编码,使用encodedOffset
被视为已弃用,并且 Range 在没有 String 本身的实例的情况下无法转换为 NSRange,因为为了计算偏移量我们需要源字符串以 UTF-8 编码,在计算偏移量之前应将其转换为 UTF-16。所以目前最好的方法是使用通用的init。
回答by Den
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))
}
回答by jriskin
Swift 3 Extension Variantthat 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
}
}
回答by orkoden
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
}
回答by Brandon A
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. So to get around all that garbage I devised the following methods to return an NSMutableAttributedString
with the highlighted words set with your color.
我喜欢 Swift 语言,但是NSAttributedString
与Range
不兼容的 Swift一起使用NSRange
让我头疼太久。因此,为了解决所有这些垃圾,我设计了以下方法来返回NSMutableAttributedString
带有您的颜色设置的突出显示的单词。
This does notwork 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