Difference between NSRange and NSMakeRange
The only difference between them is that
NSRange(location: 0, length: 5)
is an initializer for NSRange
while
NSMakeRange(0, 5)
is a function which creates a new NSRange
instance (by using the same initializer inside most likely) and actually is redundant in Swift
. Swift
has simply inherited it from Objective-C
. I would stick to the former
NSRange from Swift Range?
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).
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".
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:
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:
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:
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:
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:
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
NSRange(substringRange, in: text)
NSRange to Range String.Index
The NSString
version (as opposed to Swift String) of replacingCharacters(in: NSRange, with: NSString)
accepts an NSRange
, so one simple solution is to convert String
to NSString
first. The delegate and replacement method names are slightly different in Swift 3 and 2, so depending on which Swift you're using:
Swift 3.0
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
let nsString = textField.text as NSString?
let newString = nsString?.replacingCharacters(in: range, with: string)
}
Swift 2.x
func textField(textField: UITextField,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String) -> Bool {
let nsString = textField.text as NSString?
let newString = nsString?.stringByReplacingCharactersInRange(range, withString: string)
}
NSRange from first occurrence until end of string
Cast your String as NSString.
You will be able to use Foundation's .rangeOfString
instead of Swift's .rangeOfString
.
The Foundation's one will return an NSRange.
Be careful though, it doesn't work the same as Swift's method with Unicode, and NSRange and Range are not compatible (although there's ways to convert them).
lint Legacy Constructor Violation: (legacy_constructor) NSMakeRange in Swift?
The modern Swift constructor for this is NSRange(location:length:)
.
NSRange(location: 0, length: textfield.text!.characters.count)
Objective C: NSRange or similar with float?
Although you could reuse one of several "pair objects" from the graphics library (you can pick from a CGPoint
or CGSize
, or their NS...
equivalents) the struct
s behind these objects are so simple that it would be better to create your own:
typedef struct FPRange {
float location;
float length;
} FPRange;
FPRange FPRangeMake(float _location, float _length) {
FPRange res;
res.location = _location;
res.length = _length;
return res;
}
Shortcut to generate an NSRange for entire length of NSString?
Function? Category method?
- (NSRange)fullRange
{
return (NSRange){0, [self length]};
}
[myString replaceOccurrencesOfString:@"replace_me"
withString:replacementString
options:NSCaseInsensitiveSearch
range:[myString fullRange]];
Why does my playground crash on NSRange()
You should be passing a UIColor
for the foreground color instead of a CGColor
.
UILabel seems to use the foreground color attribute slightly differently when drawing the string based on whether the attribute covers the entire range of the string or just a subrange.
The version it uses when the attribute covers the entire string only works with UIColor
but the version it uses when the attribute only covers a substring seems to also work with CGColor
(though this behavior isn't documented so it shouldn't be relied on) which explains why adding the -1 to the range avoids the exception.
Related Topics
Why Is the Swift Compiler Marking This as an Error
Core Data Predicate Not Working
How to Delete an Item in a Collection View with a Button in the Cell
How to Assign an Array to a Class Property by Reference Rather Than a Copy
Arkit -Drop a Shadow of 3D Object on the Plane Surface
Pdfview Does Not Display My PDF
How to Get the Current Queue Name in Swift 3
How to Get Multiple Lines of Stdin Swift Hackerrank
How to Find the Index of an Item in a Multidimensional Array Swiftily
Is There a Daylight Savings Check in Swift
Swift 2.0 'Inout' Function Parameters and Computed Properties
Physicsbody: Could Not Create Physics Body
My Uiviewcontroller Is Not Filling the Entire Screen
How to Declare Swift Implicitly Unwrapped Optional as a Constant
\N in the End of Line with Using Print