Underline part of a string using NSMutableAttributedString in iOS 8 is not working
Asked Answered
R

8

72

I try to underline part of a string, for example, a 'string' part in 'test string' string. I'm using NSMutableAttributedString and my solution was working well on iOS7.

NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]
        initWithString:@"test string"];
[attributedString addAttribute:NSUnderlineStyleAttributeName
                         value:@(NSUnderlineStyleSingle)
                         range:NSMakeRange(5, 6)];
myLabel.attributedText = attributedString;

The problem is that my solution is not working in iOS8 anymore. After spending an hour on testing multiple variants of NSMutableAttributedString, I found out that this solution works only when range starts with 0 (length can differ). What is the reason for that? How can I workaround this?

Roth answered 1/10, 2014 at 7:24 Comment(2)
resolve solution without appending : https://mcmap.net/q/275881/-nsmutableattributedstring-39-s-attribute-nsstrikethroughstyleattributename-doesn-39-t-work-correctly-in-ios8Outshoot
As swift 5.1, you can look my answer #28053834Unhopedfor
T
156

Update: By investigating this question: Displaying NSMutableAttributedString on iOS 8 I finally found the solution!

You should add NSUnderlineStyleNone at the beginning of the string.

Swift 4.2 (none was removed):

let attributedString = NSMutableAttributedString()
attributedString.append(NSAttributedString(string: "test ",
                                           attributes: [.underlineStyle: 0]))
attributedString.append(NSAttributedString(string: "s",
                                           attributes: [.underlineStyle: NSUnderlineStyle.single.rawValue]))
attributedString.append(NSAttributedString(string: "tring",
                                           attributes: [.underlineStyle: 0]))

Objective-C:

 NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init];
 [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:@"test "
                                                                          attributes:@{NSUnderlineStyleAttributeName: @(NSUnderlineStyleNone)}]];
 [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:@"s"
                                                                         attributes:@{NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle),
                                                                                      NSBackgroundColorAttributeName: [UIColor clearColor]}]];
 [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:@"tring"]];

Another bonus of such approach is absence of any ranges. Very nice for localized strings.

Seems like it is Apple bug :(

Tab answered 1/10, 2014 at 8:4 Comment(8)
I thought it was working, but unfortunately I ran the app in 7.1 simulator... Still not working on iOS8. But avoiding range is very nice, thanks!Roth
To be honest, I didn't tested my code on iOS 8. But now I've done, and have finally found the solution.Tab
how can remove underline after addBistoury
@HRaval, create a new string without underline.Tab
but how can i direct text is underlined or not?Bistoury
hey @Tab thanks for this answer. Just a small correction .underlineStyle: NSUnderlineStyle.single.rawValue,Supremacy
@JeremyPiednoel, big thanks! Updated the answer for Swift 4.2.Tab
The magic here is .rawValue on the NSUnderlineStyle "enum", passing in NSUnderlineStyle.single does nothing.Libation
B
22

I found that if you apply UnderlineStyleNone to the whole string you can then selectively apply underline to a part that starts in the middle:

func underlinedString(string: NSString, term: NSString) -> NSAttributedString {
    let output = NSMutableAttributedString(string: string)
    let underlineRange = string.rangeOfString(term)
    output.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleNone.rawValue, range: NSMakeRange(0, string.length))
    output.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: underlineRange)

    return output
}
Baumgardner answered 15/3, 2015 at 11:25 Comment(1)
how can remove underline after addBistoury
S
6
NSMutableAttributedString *signUpString = [[NSMutableAttributedString alloc] initWithString:@"Not a member yet?Sign Up now"];

[signUpString appendAttributedString:[[NSAttributedString alloc] initWithString:@" "attributes:@{NSUnderlineStyleAttributeName: @(NSUnderlineStyleNone)}]];

[signUpString addAttributes: @{NSForegroundColorAttributeName:UIColorFromRGB(0x43484B),NSUnderlineStyleAttributeName:[NSNumber numberWithInteger:NSUnderlineStyleSingle]} range:NSMakeRange(17,11)];

signUpLbl.attributedText = [signUpString copy];

It worked for me

Soandso answered 29/1, 2015 at 8:42 Comment(0)
T
5

It's September 2018, so this answer is not about iOS8 but it still relates to underlining part of a string.

Here is a Swift 4 extension that underlines a given term within an already composed myAttributedString

extension NSMutableAttributedString {

    func underline(term: String) {

        guard let underlineRange = string.range(of: term) else {

            return
        }

        let startPosition = string.distance(from: term.startIndex, to: underlineRange.lowerBound)
        let nsrange = NSRange(location: startPosition, length: term.count)

        addAttribute(
            .underlineStyle,
            value: NSUnderlineStyle.styleSingle.rawValue,
            range: nsrange)
    }
}

Usage: myAttributedString.underline(term: "some term")

Terrapin answered 26/9, 2018 at 10:51 Comment(0)
P
2

Swift 5 version of @Joss's answer with few modifications, by adding a returned NSMutableAttributedString, because I couldn't use the original solution without it.

extension NSMutableAttributedString {

func underline(term: String) -> NSMutableAttributedString {
    guard let underlineRange = string.range(of: term) else {
        return NSMutableAttributedString()
    }
    let startPosition = string.distance(from: term.startIndex, to: underlineRange.lowerBound)
    let nsrange = NSRange(location: startPosition, length: term.count)
    addAttribute(
        .underlineStyle,
        value: NSUnderlineStyle.single.rawValue,
        range: nsrange)
    return self
    }   
}

Usage:

 let myUnderLinedText = "Hello World"
 let underLinedMutableString =  NSMutableAttributedString(string: myUnderLinedText, attributes: titleAttributes).underline(term: myUnderLinedText)
Pessary answered 2/8, 2019 at 8:5 Comment(0)
B
1
let values = NSMutableAttributedString(string: "**YourString**")
let range = NSRange(location: 0, length: values.length)
values.addAttribute(.underlineStyle, value: 1, range: range)

This will make the string with underlined style, also you can replace the .underlinestyle and use .link to show it as a hyperlink in blue color

Blakeblakelee answered 7/12, 2021 at 17:52 Comment(0)
G
0

I used the following extension (using exidy's function) in playground/simulator and it worked fine , you may change/add attributes depending on your needs

 extension NSMutableAttributedString
{


    func changeWordsColour(terms:[NSString])
{
    let string = self.string as NSString
    self.addAttribute(NSForegroundColorAttributeName, value: UIColor.brownColor(), range: NSMakeRange(0, self.length))
    for term in terms
    {
        let underlineRange = string.rangeOfString(term as String)
        self.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: underlineRange)

    }
}
}

 let myStr = NSMutableAttributedString(string: "Change Words Colour")
 myStr.changeWordsColour(["change","Colour"])
Grouchy answered 29/5, 2016 at 1:46 Comment(0)
B
-2

Add color for underline attribute:​​​

[attributedString addAttribute:NSUnderlineColorAttributeName
                     value:[UIColor redColor]
                     range:NSMakeRange(5, 6)];
Beware answered 1/10, 2014 at 7:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.