I hav written two fuction to Add underline to a String and remove underline :
I want to toggle underline with a button.
So how do I check if NSMutableAttributedString has underline attribute :
func addUlnTxtFnc(TxtPsgVal :String) -> NSMutableAttributedString
{
let TxtRngVal = NSMakeRange(0, TxtPsgVal.characters.count)
let TxtUnlVal = NSMutableAttributedString(string: TxtPsgVal)
TxtUnlVal.addAttribute(NSUnderlineStyleAttributeName, value:NSUnderlineStyle.StyleSingle.rawValue, range: TxtRngVal)
return TxtUnlVal
}
func rmvUlnTxtFnc(TxtPsgVal :NSMutableAttributedString) -> NSMutableAttributedString
{
let TxtRngVal = NSMakeRange(0, TxtPsgVal.string.Len())
TxtPsgVal.removeAttribute(NSUnderlineStyleAttributeName, range: TxtRngVal)
return TxtPsgVal
}
You can check by calling the function .enumerateAttribute(attrName:, inRange:, options:, usingBlock:) on the NSMutableAttributedString you get in as the parameter:
func rmvUlnTxtFnc(TxtPsgVal: NSMutableAttributedString) -> NSMutableAttributedString {
let TxtRngVal = NSMakeRange(0, TxtPsgVal.length)
TxtPsgVal.enumerateAttribute(NSUnderlineStyleAttributeName, inRange: TxtRngVal, options: .LongestEffectiveRangeNotRequired) { attribute, range, pointer in
if attribute != nil {
TxtPsgVal.removeAttribute(NSUnderlineStyleAttributeName, range: range)
}
}
return TxtPsgVal
}
Also, you can shorten your first function to a single line:
func addUlnTxtFnc(TxtPsgVal: String) -> NSMutableAttributedString {
return NSMutableAttributedString(string: TxtPsgVal, attributes: [NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue])
}
As an aside, your code does not conform to the Swift style guidelines proposed by the Swift community.
Related
I'm trying to implement an editor that can handle hashtag while typing.
extension UITextView {
func resolveHashTags() {
if self.text.isEmpty {
let emptyString = NSMutableAttributedString(string: " ", attributes: [NSAttributedString.Key.foregroundColor: UIColor.black,
NSAttributedString.Key.font: self.font!])
self.attributedText = emptyString
self.textColor = .black
self.text = ""
return
}
let cursorRange = selectedRange
let nsText = NSString(string: self.text)
let words = nsText.components(separatedBy: CharacterSet(charactersIn: "##ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_").inverted).filter({!$0.isEmpty})
self.textColor = .black
let attrString = NSMutableAttributedString()
attrString.setAttributedString(self.attributedText)
attrString.addAttributes([NSAttributedString.Key.foregroundColor : UIColor.black], range: nsText.range(of: self.text))
var anchor: Int = 0
for word in words {
// found a word that is prepended by a hashtag!
// homework for you: implement #mentions here too.
let matchRange:NSRange = nsText.range(of: word as String, range: NSRange(location: anchor, length: nsText.length - anchor))
anchor = matchRange.location + matchRange.length
if word.hasPrefix("#") {
// a range is the character position, followed by how many characters are in the word.
// we need this because we staple the "href" to this range.
// drop the hashtag
let stringifiedWord = word.dropFirst()
if let firstChar = stringifiedWord.unicodeScalars.first, NSCharacterSet.decimalDigits.contains(firstChar) {
// hashtag contains a number, like "#1"
// so don't make it clickable
} else {
// set a link for when the user clicks on this word.
// it's not enough to use the word "hash", but you need the url scheme syntax "hash://"
// note: since it's a URL now, the color is set to the project's tint color
attrString.addAttribute(NSAttributedString.Key.link, value: "hash:\(stringifiedWord)", range: matchRange)
}
} else if !word.hasPrefix("#") {
}
}
self.attributedText = attrString
self.selectedRange = cursorRange
}
}
So this is the extension I'm using to create a hyperlink in UITextView. Called in func textViewDidChange(_ textView: UITextView)
So while typing if any word starts with #. It'll turn in hyperlinks and will change color to blue. After typing the intended word if you press space it goes back to black text. This is expected behavior.
But if you clear text and move your course back to hashtag word like this
it keeps extending hyperlink to the next word too.
any solution to keep hyperlinks to that word only. Anything typed after hashtag should be normal text
I finally figured it out.
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
var shouldReturn = true
let selectedRange = textView.selectedRange
let attributedText = NSMutableAttributedString(attributedString: textView.attributedText)
if !text.isEmpty && text != " " {
var userAttributes = [(NSAttributedString.Key, Any, NSRange)]()
attributedText.enumerateAttribute(.link, in: _NSRange(location: 0, length: textView.text.count), options: .longestEffectiveRangeNotRequired) { (value, range, stop) in
if let url = value as? String, url.hasPrefix("user:") {
userAttributes.append((.link, value!, range))
}
}
if let userLink = userAttributes.first(where: {$0.2.contains(range.location - 1)}) {
attributedText.replaceCharacters(in: range, with: NSAttributedString(string: text, attributes: [NSAttributedString.Key.link : userLink.1, NSAttributedString.Key.font : textView.font as Any]))
textView.attributedText = attributedText
shouldReturn = false
} else {
attributedText.replaceCharacters(in: range, with: NSAttributedString(string: text, attributes: [NSAttributedString.Key.font : textView.font as Any]))
textView.attributedText = attributedText
textDidChange?(textView)
shouldReturn = false
}
textView.selectedRange = _NSRange(location: selectedRange.location + text.count, length: 0)
textViewDidChange(textView)
}
return shouldReturn
}
This way I have the control to update the link in between the word and it doesn't extend afterward to a new word.
This question already has answers here:
Replace UITextViews text with attributed string
(4 answers)
Closed 6 years ago.
I have a textView, and I am trying to give it an attributed text. I tried achieving it inside shouldChangeTextInRange, but it crashes for range out of index.
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
if myTextView {
textView.attributedText = addAttributedText(1, text: text, fontsize: 13)
let newText = (textView.text as NSString).stringByReplacingCharactersInRange(range, withString: text)
let numberOfChars = newText.characters.count
return numberOfChars < 20
}
return true
}
func addAttributedText(spacing:CGFloat, text:String, fontsize: CGFloat) -> NSMutableAttributedString {
let attributedString = NSMutableAttributedString(string: text, attributes: [NSFontAttributeName:UIFont(
name: "Font",
size: fontsize)!])
attributedString.addAttribute(NSKernAttributeName, value: spacing, range: NSMakeRange(0, text.characters.count))
return attributedString
}
I tried adding attributedString with empty text to textView in viewDidLoad, but that doesn't help. That's why I thought it would be appropriate to do it on shouldChangeTextInRange
(Please note that my addAttributedText method works perfectly for other textviews)
If I use this, in one character type-in, it writes 2x and crashes. What is the right way of handling that kind of converting textView's text to attributed text that is being typed.
Here is the code that I tried to convert from the link above, it might have bugs, but I hope it will be able to help you.
func formatTextInTextView(textView: UITextView)
{
textView.scrollEnabled = false
var selectedRange: NSRange = textView.selectedRange
var text: String = textView.text!
// This will give me an attributedString with the base text-style
var attributedString: NSMutableAttributedString = NSMutableAttributedString(string: text)
var error: NSError? = nil
var regex: NSRegularExpression = NSRegularExpression.regularExpressionWithPattern("#(\\w+)", options: 0, error: error!)
var matches: [AnyObject] = regex.matchesInString(text, options: 0, range: NSMakeRange(0, text.length))
for match: NSTextCheckingResult in matches {
var matchRange: NSRange = match.rangeAtIndex(0)
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: matchRange)
}
textView.attributedText = attributedString
textView.selectedRange = selectedRange
textView.scrollEnabled = true
}
EDIT: didn't see that in the original post there was a Swift answer, here is the link: stackoverflow.com/a/35842523/1226963
I want NSTextView object to react to Tab key hit by changing NSParagraphStyle spacing. And it does but EXTREMELY slow!!! In fact if I do this changes too quick (hit Tab key too fast), I eventually got glitches that sometimes even lead to crash. Here's the video: https://drive.google.com/open?id=0B4aMQXnlIOvCUXNjWTVXVkR3NHc. And another one: https://drive.google.com/open?id=0B4aMQXnlIOvCUDJjSEN0bFdqQXc
Fragment of code from my NSTextStorage subclass:
override func attributesAtIndex(index: Int, effectiveRange range: NSRangePointer) -> [String : AnyObject] {
return storage.attributesAtIndex(index, effectiveRange: range)
}
override func replaceCharactersInRange(range: NSRange, withString str: String) {
let delta = str.characters.count - range.length
beginEditing()
storage.replaceCharactersInRange(range, withString:str)
edited([.EditedCharacters, .EditedAttributes], range: range, changeInLength: delta)
endEditing()
}
override func setAttributes(attrs: [String : AnyObject]!, range: NSRange) {
beginEditing()
storage.setAttributes(attrs, range: range)
edited(.EditedAttributes, range: range, changeInLength: 0)
endEditing()
}
Fragment of code from my NSTextView subclass:
override func shouldChangeTextInRange(affectedCharRange: NSRange, replacementString: String?) -> Bool {
super.shouldChangeTextInRange(affectedCharRange, replacementString: replacementString)
guard replacementString != nil else { return true }
if replacementString == "\t" {
defer {
let selectedRangesValues = self.selectedRanges
var selectedRanges = [NSRange]()
for value in selectedRangesValues {
selectedRanges.append(value.rangeValue)
}
textController.switchToAltAttributesInRange(selectedRanges)
}
return false
}
return true
}
Fragment of code from my TextController which creates and applies alternative attributes:
func switchToAltAttributesInRange(ranges : [NSRange]) {
// get paragraph indexes from the ranges
var indexes = [Int]()
for range in ranges {
for idx in textStorage.paragraphsInRange(range) {
indexes.append(idx)
}
}
// change attributes for all the paragraphs in those ranges
for index in indexes {
let paragraphRange = textStorage.paragraphRangeAtIndex(index)
let element = elementAtIndex(index)
let altElementType = altElementTypeForElementType(element.type)
// change the attributes
let newAttributes = paragraphAttributesForElement(type: altElementType.rawValue)
self.textStorage.beginUpdates()
self.textStorage.setAttributes(newAttributes, range: paragraphRange)
self.textStorage.endUpdates()
}
}
func paragraphAttributesForElement(type typeString: String) -> [String : AnyObject] {
let elementPreset = elementPresetForType(elementType)
// set font
let font = NSFont (name: elementPreset.font, size: CGFloat(elementPreset.fontSize))!
// set attributes
let elementAttributes = [NSFontAttributeName: font,
NSParagraphStyleAttributeName : paragraphStyleForElementPreset(elementPreset, font: font),
NSForegroundColorAttributeName: NSColor.colorFromHexValue(elementPreset.color),
NSUnderlineStyleAttributeName : elementPreset.underlineStyle,
ElementAttribute.AllCaps.rawValue : elementPreset.allCaps]
return elementAttributes
}
func paragraphStyleForElementPreset(elementPreset : TextElementPreset, font : NSFont) -> NSParagraphStyle {
let sceneParagraphStyle = NSMutableParagraphStyle()
let spacing = elementPreset.spacing
let spacingBefore = elementPreset.spacingBefore
let headIndent = elementPreset.headIndent
let tailIndent = elementPreset.tailIndent
let cFont = CTFontCreateWithName(font.fontName, font.pointSize, nil)
let fontHeight = CTFontGetDescent(cFont) + CTFontGetAscent(cFont) + CTFontGetLeading(cFont)
sceneParagraphStyle.paragraphSpacingBefore = CGFloat(spacingBefore)
sceneParagraphStyle.paragraphSpacing = fontHeight * CGFloat(spacing)
sceneParagraphStyle.headIndent = ScreenMetrics.pointsFromInches(CGFloat(headIndent))
sceneParagraphStyle.tailIndent = ScreenMetrics.pointsFromInches(CGFloat(tailIndent))
sceneParagraphStyle.firstLineHeadIndent = sceneParagraphStyle.headIndent
sceneParagraphStyle.lineBreakMode = .ByWordWrapping
return sceneParagraphStyle
}
Time Profiler instrument shows a high peak when I press the Tab key. It says that NSTextStorage attributesAtIndex takes up to 40 ms each time I press the Tab key.
I checked: if I remove NSParagraphStyle changes, everything becomes normal. So the question is: how should I update paragraph styles?
Hmm... Didn't found such a solution neither in Apple docs or in Google... Just have experimented and turns out if I add textView.display() after call of self.textStorage.setAttributes, everything works fine!
UPDATE: setNeedsDisplay(invalidRect) does a much better job because you might redraw just a dirty portion of text view's visible rect
How to add strikethroughStyle for label. A middle delete line to a number in UILabel. Check the attach image for reference.
Use attributeString
Screenshot
Code
NSDictionary * attribtues = #{NSStrikethroughStyleAttributeName:#(NSUnderlineStyleSingle),
NSStrikethroughColorAttributeName:[UIColor redColor]};
NSAttributedString * attr = [[NSAttributedString alloc] initWithString:#"label" attributes:attribtues];
self.label.attributedText = attr;
Updated function from #krunal answer for Swift4
extension String {
func strikeThrough() -> NSAttributedString {
let attributeString = NSMutableAttributedString(string: self)
attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: NSMakeRange(0,attributeString.length))
return attributeString
}
}
and to call the function is the same
yourLabel.attributedText = "yourString".strikeThrough()
For Programatically Use Below Code
Prefer to Use Extension:
extension String {
func strikeThrough() -> NSAttributedString {
let attributeString = NSMutableAttributedString(string: self)
attributeString.addAttribute(NSAttributedStringKey.strikethroughStyle, value: NSUnderlineStyle.styleSingle.rawValue, range: NSMakeRange(0,attributeString.length))
return attributeString
}
}
And call this Function
myLabel.attributedText = "my string".strikeThrough()
And Result look like this.
If wanting to use a UILabel extension
extension UILabel {
func strikeThroughText() {
let attributeString = NSMutableAttributedString(string: self.text ?? "")
attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: NSMakeRange(0,attributeString.length))
self.attributedText = attributeString
}
}
add middle delete line for UILabel in storyboard
tap uilabel in storyboard, look at utilities. Select Attributed
tap font icon, open font panel.
Select the text you want to change
Select middle line (ex: Single)
completed.
extension String {
func strikeThrough() -> NSAttributedString {
let attributeString = NSMutableAttributedString(string: self)
attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: NSMakeRange(0,attributeString.length))
return attributeString
} }
Then in 'viewDidLoad' View Controller
yourLabel.attributedText = "yourString".strikeThrough()
I'm trying to set some attributes of a string in code, but can't get NSAttributedString to work. This is the function, that's supposed to change the string:
func getAttributedString(string: String) -> NSAttributedString
{
var attrString = NSMutableAttributedString(string: string)
var attrs = [NSFontAttributeName : UIFont.boldSystemFontOfSize(18.0)]
attrString.setAttributes(attrs, range: NSMakeRange(0, attrString.length))
return attrString
}
And this is how I use it:
if (self.product.packageDimensions != nil) {
self.descriptionLabel.text =
self.descriptionLabel.text + self.getAttributedString("Package dimensions:").string +
"\n\(self.product.packageDimensions) \n"
}
But the font stays the same. What am I doing wrong ?
You make 2 errors in your code.
setAttributes needs a Dictionary, not an Array
when you use the string attribute, you will only get a String, all attributes are lost.
To add or change attributes to a attributedString it has to be mutable. You only get a NSMutableString from the attributedText attribute. If you want to change it create a mutable version from it and change it. Then you may set the attributedText to the new mutable version.
If you can give the attributed string as an argument, I will give you an example that works:
func setFontFor(attrString: NSAttributedString) -> NSMutableAttributedString {
var mutableAttrString: NSMutableAttributedString = NSMutableAttributedString(attributedString: attrString)
let headerStart: Int = 0
let headerEnd: Int = 13
mutableAttrString.addAttribute(NSFontAttributeName, value: UIFont.boldSystemFontOfSize(18.0), range: NSMakeRange(headerStart, headerEnd))
return mutableAttrString
}
Usage:
myLabel.attributedText = setFontFor(myLabel.attributedText)
As you can see I used the attributedText property of the UILabel class, it also works for UITextView and others. If you have another label, you can create a new NSAttributedString with the initializer NSAttributedString(normalString) as you already used in the question code.
if (self.product.packageDimensions != nil) {
self.descriptionLabel.attributedText =
self.descriptionLabel.attributedText + self.getAttributedString("Package dimensions:").string +
"\n\(self.product.packageDimensions) \n"
}
You should use the attributedText method