Home > Net >  Swift- How to remove attributed string values and make it appear as normal string. (Bold, Italic, Un
Swift- How to remove attributed string values and make it appear as normal string. (Bold, Italic, Un

Time:12-16

I want to remove attributed string values and make it appear as normal string. (Bold, Italic, Underline, Strikethrough)

I have added below code to make it attributed but I want to know how do i remove these attributes from the string when clicked on that action again.

Bold - 

   if textBold == true {
                            let string = text
                            let attributes: [NSAttributedString.Key: Any] = [
                                .font: UIFont.boldSystemFont(ofSize: 25)
                            ]
                            let attributedString = NSAttributedString(string: string, attributes: attributes)
                            modifiedString = attributedString
                            text_View.attributedText = modifiedString

                            text_View.sizeToFit()
                            databaseHandlerObj.editLabelData(textProjectId: fetchTextProjectId, text_Id: fetchTextId, text: modifiedString.string)
                        }

Italic - 
        if textItalic == true {

                            text_View.sizeToFit()
                            let string = text
                            
                            let attributes: [NSAttributedString.Key: Any] = [
                                .font: UIFont.italicSystemFont(ofSize: CGFloat(fontSize))
                            ]
                            let attributedString = NSAttributedString(string: string, attributes: attributes)
                            self.modifiedString = attributedString
                            text_View.attributedText = modifiedString
                            
                            databaseHandlerObj.editLabelData(textProjectId: fetchTextProjectId, text_Id: fetchTextId, text: modifiedString.string)
                        }

Underline -
   if textUnderline == true {
                            text_View.sizeToFit()
                            let string = text
                            let attributedString = NSMutableAttributedString.init(string: string)
                            attributedString.addAttribute(NSAttributedString.Key.underlineStyle, value: 1, range:
                                                            NSRange.init(location: 0, length: attributedString.length))
                            self.modifiedString = attributedString
                            text_View.attributedText = modifiedString
                            
                            databaseHandlerObj.editLabelData(textProjectId: fetchTextProjectId, text_Id: fetchTextId, text: modifiedString.string)
                        }
                        

Strikethrough - 
      if textStrikethrough == true {
                            text_View.sizeToFit()
                            let string = text
                            let attributeString: NSMutableAttributedString =  NSMutableAttributedString(string: string)
                            attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: 2, range: NSMakeRange(0, attributeString.length))
                            self.modifiedString = attributeString
                            text_View.attributedText = modifiedString
                            databaseHandlerObj.editLabelData(textProjectId: fetchTextProjectId, text_Id: fetchTextId, text: modifiedString.string)
                        }

CodePudding user response:

You need to use a NSMutableAttributedString. On it you can use the removeAttribute(_ name: NSAttributedString.Key, range: NSRange) method where you specify the range e.g. the whole string where you want to remove the attribute.

Apple documentation

CodePudding user response:

You need to enumerate the attributes on the NSAttributedString, and change or remove them.

With a little help from another question to know if a font is bold/italic

extension UIFont {
    var isBold: Bool {
        return fontDescriptor.symbolicTraits.contains(.traitBold)
    }

    var isItalic: Bool {
        return fontDescriptor.symbolicTraits.contains(.traitItalic)
    }
}

To simplify "your true logic":

extension NSAttributedString {

    enum Effects {
        case bold
        case italic
        case underline
        case strikethrough
    }

    var fullRange: NSRange {
        NSRange(location: 0, length: length)
    }
}

This should do the trick:

func remove(effects: NSAttributedString.Effects, on attributedString: NSAttributedString, range: NSRange) -> NSAttributedString {

    let mutableAttributedString = NSMutableAttributedString(attributedString: attributedString)

    mutableAttributedString.enumerateAttributes(in: range, options: []) { attributes, subrange, pointee in
        switch effects {
        case .bold:
            if let currentFont = attributes[.font] as? UIFont, currentFont.isBold {
                let newFont = UIFont.systemFont(ofSize: currentFont.pointSize)
                // Since we can only have one attribute at a range (and all subranges including in it)
                // there is no need to remove then add, adding it will replace the previous one
                mutableAttributedString.addAttribute(.font, value: newFont, range: subrange)
            }
        case .italic:
            if let currentFont = attributes[.font] as? UIFont, currentFont.isItalic {
                let newFont = UIFont.systemFont(ofSize: currentFont.pointSize)
                // Since we can only have one attribute at a range (and all subranges including in it)
                // there is no need to remove then add, adding it will replace the previous one
                mutableAttributedString.addAttribute(.font, value: newFont, range: subrange)
            }
        case .strikethrough:
            if attributes[.strikethroughStyle] != nil {
                mutableAttributedString.removeAttribute(.strikethroughStyle, range: subrange)
            }
            if attributes[.strikethroughColor] != nil { 
                mutableAttributedString.removeAttribute(.strikethroughColor, range: subrange)
            }
        case .underline:
            if attributes[.underlineColor] != nil {
                mutableAttributedString.removeAttribute(.underlineColor, range: subrange)
            }
            if attributes[.underlineStyle] != nil {
                mutableAttributedString.removeAttribute(.underlineStyle, range: subrange)
            }
        }
    }
    return mutableAttributedString
}

In Playground, you can test with:

let boldText = NSAttributedString(string: "Bold text", attributes: [.font: UIFont.boldSystemFont(ofSize: 25)])
let unboldText = remove(effects: .bold, on: boldText, range: boldText.fullRange)
let italicText = NSAttributedString(string: "Italic text", attributes: [.font: UIFont.italicSystemFont(ofSize: 25)])
let unItalicText = remove(effects: .italic, on: italicText, range: italicText.fullRange)
let underlineText = NSAttributedString(string: "Underline text", attributes: [.underlineStyle: 1])
let unUnderlineText = remove(effects: .underline, on: underlineText, range: underlineText.fullRange)
let strikethroughText = NSAttributedString(string: "Strikethrough text", attributes: [.strikethroughStyle: 2])
let unStrikethroughText = remove(effects: .strikethrough, on: strikethroughText, range: strikethroughText.fullRange)

let attributedStrings = [boldText, unboldText, italicText, unItalicText, underlineText, unUnderlineText, strikethroughText, unStrikethroughText]

let fullAttributedString = attributedStrings.reduce(into: NSMutableAttributedString()) {
    $0.append(NSAttributedString(string: "\n"))
    $0.append($1)
}

The following test is to ensure that if there are other effects on the `NSAttributedString`, they aren't removed.

let textView = UITextView(frame: CGRect(x: 0, y: 0, width: 500, height: 200))
textView.backgroundColor = .yellow
textView.attributedText = fullAttributedString
textView
textView.attributedText = remove(effects: .bold, on: fullAttributedString, range: fullAttributedString.fullRange)
textView
textView.attributedText = remove(effects: .italic, on: fullAttributedString, range: fullAttributedString.fullRange)
textView
textView.attributedText = remove(effects: .underline, on: fullAttributedString, range: fullAttributedString.fullRange)
textView
textView.attributedText = remove(effects: .strikethrough, on: fullAttributedString, range: fullAttributedString.fullRange)
textView

Nota bene: You currently are doing bold OR italic, not both at the same time, since it's in this property is inside the UIFont, you'd need to find a bold AND italic font at the same time.
So if you want to remove an italic from a italic AND bold, you'd need to get the bold. I consider it as another scope, but there are questions about that.

Nota bene 2:

When you want to remove the attributes, I check for the presence of the attributes, but there is no need:

So:

if attributes[.someAttribute] != nil {
    mutableAttributedString.removeAttribute(.someAttribute, range: subrange)
}

Could just be:

mutableAttributedString.removeAttribute(.someAttribute, range: subrange)

Nota bene 3:

func remove(effects: NSAttributedString.Effects, on attributedString: NSAttributedString, range: NSRange) -> NSAttributedString

Could be an extension on NSAttributedString/NSMutableAttributedString instead.

  • Related