Swift 1.2 NSTextStorage removeAttribute with Range<String.Index> - ios

I have a subclass of NSTextStorage and I'm trying to remove the foreground color of a paragraph the following way:
var paragraphRange = self.string.paragraphRangeForRange(
advance(self.string.startIndex, theRange.location)..advance(self.string.startIndex, theRange.location + theRange.length))
self.removeAttribute(NSForegroundColorAttributeName, range: paragraphRange)
However, I get the following error Cannot invoke 'removeAttribute' with an argument list of type '(String, range: (Range<String.Index>))'
Help Please. I think TextKit on Swift is a mess. Some methods receive/return NSRange but String works with Range<String.Index> making it a hell to work with.

The problem here is that the NSString returned by self.string
is automatically bridged to a Swift String. A possible solution is
to convert it back to NSString explicitly:
func removeColorForRange(theRange : NSRange) {
let paragraphRange = (self.string as NSString).paragraphRangeForRange(theRange)
self.removeAttribute(NSForegroundColorAttributeName, range: paragraphRange)
}
Note also that the range operator .. has been replaced by ..<
in newer Swift versions (to avoid confusion with ... and to
emphasize that the upper bound is not included).

Related

Return substring from attributed string using range in Swift [duplicate]

This question already has answers here:
NSRange to Range<String.Index>
(16 answers)
Closed 3 years ago.
I am trying to get a substring from a string using the range without luck. Having searched high and low, I can't find a way to do this seemingly straightforward task in Swift. The range is in the form of an NSRange obtained from a delegate method.
In Objective-c, if you have a range, you could do:
NSString * text = "Hello World";
NSString *sub = [text substringWithRange:range];
According to this answer, the following should work in Swift:
let mySubstring = text[range] // play
let myString = String(mySubstring)
However, when I try this, I get an error:
Cannot subscript a value of type 'String' with an index of type
'NSRange' (aka '_NSRange')
I think the issue may have to do with using an NSRange instead of a range but I can't figure out how to get it to work. Thanks for any suggestions.
The thing is you can not subscript a String using a NSRange, you have to use a Range. Try the following out:
let newRange = Range(range, in: text)
let mySubstring = text[newRange]
let myString = String(mySubstring)
Please read your linked question one more time.
You will notice that String in Swift doesn't work with Range<Int> but with Range<String.Index> and definitely not with NSRange
Example of using range on string:
let text = "Hello world"
let from = text.index(after: text.startIndex)
let to = text.index(from, offsetBy: 4)
text[from...to] // ello

Creating closed Range in Swift 3 not working

Can anyone tell me why the code below works in Swift 2, but somehow breaks in Swift 3?
let range: Range = 0...2
However it can simply be fixed by doing this
let range: Range = 0..<3
Anyone knows what is the reason behind this?
Operators ... and ..< used to produce the same type, Range, in Swift 2.x. Now they produce different types (migration guide):
Range
CountableRange
ClosedRange
CountableClosedRange
Changing the type in the first assignment to ClosedRange should fix the problem. Better yet, let Swift infer the type for you:
let range = 0...2

"Cannot Increment endIndex" because of emoji

I have a function that finds the current word a user has selected in a UITextView. However, if I call this function when an emoji is in the UITextView.text property, I see a crash. I believe this is because of the different character counts in String vs NSString.
How do I properly convert this?
func currentWord() -> String {
let cursorPosition = selectedRange.location
let separationCharacters = NSCharacterSet(charactersInString: " ")
// crash occurs here
let beginRange = Range(text.startIndex.advancedBy(0) ..< text.startIndex.advancedBy(cursorPosition))
let endRange = Range(text.startIndex.advancedBy(cursorPosition) ..< text.startIndex.advancedBy(text.characters.count))
let beginPhrase = text.substringWithRange(beginRange)
let endPhrase = text.substringWithRange(endRange)
let beginWords = beginPhrase.componentsSeparatedByCharactersInSet(separationCharacters)
let endWords = endPhrase.componentsSeparatedByCharactersInSet(separationCharacters)
return beginWords.last! + endWords.first!
}
I believe this is because of the different character counts in String vs NSString
You're right about that. You are shifting back and forth between using NSRange (Cocoa) and Range (Swift) — and they work differently. And NSString (Cocoa) and String (Swift) have different ideas of where the character boundaries are. You need to be consistent.
Once you've used selectedRange in the first line, you are in the Cocoa world of NSRange. You need to stay consistently in the Cocoa world. Don't use any Swift Ranges! Don't use any Swift characters!
Form your beginRange entirely using NSRange — for example, call NSMakeRange. Don't use characters.count; stay in the NSString world and use the string's length (in Swift, that is its utf16.count). Then all will be well.

Changing font of strings separated by spaces

I'm trying to make the words split by spaces green in a UITextField, kind of like the way it works when you compose of a new iMessage. I commented out the part of my code that's giving me a runtime error. Please let me know if you have any ideas:
func textChanged(sender : UITextField) {
var myMutableString = NSMutableAttributedString()
let arr = sender.text!.componentsSeparatedByString(" ")
var c = 0
for i in arr {
/*
myMutableString.addAttribute(NSForegroundColorAttributeName, value: UIColor.greenColor(), range: NSRange(location:c,length:i.characters.count))
sender.attributedText = myMutableString
*/
print(c,i.characters.count)
c += i.characters.count + 1
}
}
Your code has at least two parts needed to be fixed.
var myMutableString = NSMutableAttributedString()
This line creates an empty NSMutableAttributedString. Any access to the content may cause runtime error.
The other is i.characters.count. You should not use Character based locations and counts, when the APIs you want use is based on the behaviour of NSString. Use UTF-16 based count.
And one more, this is not critical, but you should use sort of meaningful names for variables.
So, all included:
func textChanged(sender: UITextField) {
let text = sender.text ?? ""
let myMutableString = NSMutableAttributedString(string: text)
let components = text.componentsSeparatedByString(" ")
var currentPosition = 0
for component in components {
myMutableString.addAttribute(NSForegroundColorAttributeName, value: UIColor.greenColor(), range: NSRange(location: currentPosition,length: component.utf16.count))
sender.attributedText = myMutableString
print(currentPosition, component.utf16.count)
currentPosition += component.utf16.count + 1
}
}
But whether this works as you expect or not depends on when this method is called.
You create an empty attributed string but never install any text into it.
The addAttribute call apples attributes to text in a string. If you try to apply attributes to a range that does not contain text, you will crash.
You need to install the content of the unattributed string into the attributed string, then apply attributes.
Note that you should probably move the line
sender.attributedText = myMutableString
Outside of your for loop. There is no good reason to install the attributed string to the text field repeatedly as you add color attributes to each word.
Note this bit from the Xcode docs on addAttribute:
Raises... an NSRangeException if any part of aRange lies beyond the
end of the receiver’s characters.
If you are getting an NSRangeException that would be a clue as to what is wrong with your current code. Pay careful attention to the error messages you get. They usually offer important clues as to what's going wrong.

Swift 2 Conversion Hell Part 327: How to tame NSMatchingOptions being nil?

I have this method in a Regex class:
func test(input:String) -> Bool
{
let matches = expression.matchesInString(input, options: nil, range: NSMakeRange(0, count(input)))
return matches.count > 0
}
Swift 2.1 tells me:
Nil is not compatible with expected argument type 'NSMatchingOptions'
Can somebody tell me how to fix this properly? NSMatchingOptions doesn't seem to offer any default empty property.
If you do not want to pass any options to the regex, use options: [].
In Swift 2 an empty OptionSetType can be represented with <Type>()
NSMatchingOptions()
or just with a pair of square brackets
[]
NSMatchingOptions is an enum, not a class, so nil makes no sense. You have to use one of the enum values, the most likely of which is Anchored. It's really not a Swift issue at all.

Resources