NSAttributedString highlight/background color shows between lines (ugly)
Asked Answered
E

3

19

I'm trying to nicely display paragraphs of highlighted in a NSTextView. Right now, I'm doing this by creating a NSAttributedString with a background color. Here's some simplified code:

NSDictionary *attributes = @{NSBackgroundColorAttributeName:NSColor.greenColor};
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:@"Here is a single line of text with single spacing" attributes:attributes];

[textView.textStorage setAttributedString:attrString];

This approach basically works, in that it produces highlighted text.

Single line single spaced

Unfortunately, when multiple lines exist, the highlight covers the vertical space between the lines in addition to the lines themselves, resulting in ugliness.

Multi line double spaced text

Does anyone know of a way to do this kind of highlighting in Cocoa? The picture below is basically what I'm looking for (ignore the shadow on the white boxes):

whiteout text

I'd be willing to use CoreText, html, or whatever is necessary to make things look nicer.

Epiphany answered 22/9, 2013 at 0:16 Comment(2)
Did you ever solve this , I am having exactly the same issueSceptic
I didn't figure this out really, but I did figure out how to at least center the selection rect around the text, so that it's not all above or below. It involves calculating the [paragraphStyle setLineSpacing:xx] and [paragraphStyle setLineHeightMultiple:xx] such that they are the same. Again, this doesn't solve the actual issue though, just makes it more tolerable.Epiphany
K
3

You will need to subclass NSLayoutManager and override:

- (void)fillBackgroundRectArray:(const CGRect *)rectArray
                      count:(NSUInteger)rectCount
          forCharacterRange:(NSRange)charRange
                      color:(UIColor *)color;

This is the primitive method for drawing background color rectangles.

Kimura answered 28/3, 2016 at 0:5 Comment(1)
How you implement the method, supra, is going to be specific to your application. But, as additional context, I used - (void)enumerateLineFragmentsForGlyphRange:(NSRange)glyphRange usingBlock:(void (^)(CGRect rect, CGRect usedRect, NSTextContainer *textContainer, NSRange glyphRange, BOOL *stop))block to create an array of CGRect structs that described the bounds used by the character glyphs, and created intersections with the rectArray contents to restrict the bounds of my highlights.Kimura
S
0

Try this:-

     -(IBAction)chooseOnlylines:(id)sender
{

 NSString *allTheText =[tv string];
    NSArray *lines = [allTheText componentsSeparatedByString:@"\n"];
    NSString *str=[[NSString alloc]init];
    NSMutableAttributedString *attr;
    BOOL isNext=YES;
    [tv setString:@""];
    for (str in lines)
    {
        attr=[[NSMutableAttributedString alloc]initWithString:str];
        if ([str length] > 0)
        {

        NSRange range=NSMakeRange(0, [str length]);
        [attr addAttribute:NSBackgroundColorAttributeName value:[NSColor greenColor] range:range];
        [tv .textStorage appendAttributedString:attr];
            isNext=YES;
        }
        else
        {
            NSString *str=@"\n";
            NSAttributedString *attr=[[NSAttributedString alloc]initWithString:str];
            [tv .textStorage appendAttributedString:attr];
            isNext=NO;
        }
        if (isNext==YES)
        {
            NSString *str=@"\n";
            NSAttributedString *attr=[[NSAttributedString alloc]initWithString:str];
            [tv .textStorage appendAttributedString:attr];

        }
     }
}
Saros answered 23/9, 2013 at 16:7 Comment(2)
Unfortunately in this case, the lines are not separated by \n characters -- this is a paragraph, so the text is automatically wrapped. Thanks for the attempt, though.Epiphany
I have modified the code. Please try now and also it will not wrape the words.Saros
N
0

The paragraph needs to be highlighted when user taps on it. this is how I implemented it and don't confuse with the highlight color, it is a custom NSAttributedString key I created for this purpose.

extension NSAttributedString.Key {
    public static let highlightColor = NSAttributedString.Key.init("highlightColor")
}

class ReaderLayoutManager: NSLayoutManager {

    // MARK: - Draw Background
    override func drawBackground(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) {
        super.drawBackground(forGlyphRange: glyphsToShow, at: origin)
        self.enumerateLineFragments(forGlyphRange: glyphsToShow) { (_, usedRect, _, range, _) in
            guard let highlightColor = self.currentHighlightColor(range: range) else { return }
            guard let context = UIGraphicsGetCurrentContext() else { return }
            var lineRect = usedRect
            lineRect.origin.y += 10
            lineRect.size.height -= 2
            context.saveGState()
            let path = UIBezierPath(roundedRect: lineRect, cornerRadius: 2)
            highlightColor.setFill()
            path.fill()
            context.restoreGState()
        }
    }

    private func currentHighlightColor(range: NSRange) -> UIColor? {
        guard let textStorage = textStorage else { return nil }
        guard let highlightColor = textStorage.attributes(at: range.location, effectiveRange: nil)[.highlightColor] as? UIColor else { return nil }
        return highlightColor
    }
}

when user clicks on it, I set the highlight color for the range and reset the TextView.

attributedString.addAttributes([.highlightColor: theme.textUnderlineColor], range: range)
Nnw answered 27/3, 2020 at 9:44 Comment(1)
it's seems didn't work.Nilla

© 2022 - 2024 — McMap. All rights reserved.