NSAttributedString background color and rounded corners

放肆的年华 提交于 2019-12-17 02:27:10

问题


I have a question regarding rounded corners and text background color for a custom UIView.

Basically, I need to achieve an effect like this (image attached - notice the rounded corners on one side) in a custom UIView:

I'm thinking the approach to use is:

  • Use Core Text to get glyph runs.
  • Check highlight range.
  • If the current run is within the highlight range, draw a background rectangle with rounded corners and desired fill color before drawing the glyph run.
  • Draw the glyph run.

However, I'm not sure whether this is the only solution (or for that matter, whether this is the most efficient solution).

Using a UIWebView is not an option, so I have to do it in a custom UIView.

My question being, is this the best approach to use, and am I on the right track? Or am I missing out something important or going about it the wrong way?


回答1:


I managed to achieve the above effect, so thought I'd post an answer for the same.

If anyone has any suggestions about making this more effective, please feel free to contribute. I'll be sure to mark your answer as the correct one. :)

For doing this, you'll need to add a "custom attribute" to NSAttributedString.

Basically, what that means is that you can add any key-value pair, as long as it is something that you can add to an NSDictionary instance. If the system does not recognize that attribute, it does nothing. It is up to you, as the developer, to provide a custom implementation and behavior for that attribute.

For the purposes of this answer, let us assume I've added a custom attribute called: @"MyRoundedBackgroundColor" with a value of [UIColor greenColor].

For the steps that follow, you'll need to have a basic understanding of how CoreText gets stuff done. Check out Apple's Core Text Programming Guide for understanding what's a frame/line/glyph run/glyph, etc.

So, here are the steps:

  1. Create a custom UIView subclass.
  2. Have a property for accepting an NSAttributedString.
  3. Create a CTFramesetter using that NSAttributedString instance.
  4. Override the drawRect: method
  5. Create a CTFrame instance from the CTFramesetter.
    1. You will need to give a CGPathRef to create the CTFrame. Make that CGPath to be the same as the frame in which you wish to draw the text.
  6. Get the current graphics context and flip the text coordinate system.
  7. Using CTFrameGetLines(...), get all the lines in the CTFrame you just created.
  8. Using CTFrameGetLineOrigins(...), get all the line origins for the CTFrame.
  9. Start a for loop - for each line in the array of CTLine...
  10. Set the text position to the start of the CTLine using CGContextSetTextPosition(...).
  11. Using CTLineGetGlyphRuns(...) get all the Glyph Runs (CTRunRef) from the CTLine.
  12. Start another for loop - for each glyphRun in the array of CTRun...
  13. Get the range of the run using CTRunGetStringRange(...).
  14. Get typographic bounds using CTRunGetTypographicBounds(...).
  15. Get the x offset for the run using CTLineGetOffsetForStringIndex(...).
  16. Calculate the bounding rect (let's call it runBounds) using the values returned from the aforementioned functions.
    1. Remember - CTRunGetTypographicBounds(...) requires pointers to variables to store the "ascent" and "descent" of the text. You need to add those to get the run height.
  17. Get the attributes for the run using CTRunGetAttributes(...).
  18. Check if the attribute dictionary contains your attribute.
  19. If your attribute exists, calculate the bounds of the rectangle that needs to be painted.
  20. Core text has the line origins at the baseline. We need to draw from the lowermost point of the text to the topmost point. Thus, we need to adjust for descent.
  21. So, subtract the descent from the bounding rect that we calculated in step 16 (runBounds).
  22. Now that we have the runBounds, we know what area we want to paint - now we can use any of the CoreGraphis/UIBezierPath methods to draw and fill a rect with specific rounded corners.
    1. UIBezierPath has a convenience class method called bezierPathWithRoundedRect:byRoundingCorners:cornerRadii: that let's you round specific corners. You specify the corners using bit masks in the 2nd parameter.
  23. Now that you've filled the rect, simply draw the glyph run using CTRunDraw(...).
  24. Celebrate victory for having created your custom attribute - drink a beer or something! :D

Regarding detecting that the attribute range extends over multiple runs, you can get the entire effective range of your custom attribute when the 1st run encounters the attribute. If you find that the length of the maximum effective range of your attribute is greater than the length of your run, you need to paint sharp corners on the right side (for a left to right script). More math will let you detect the highlight corner style for the next line as well. :)

Attached is a screenshot of the effect. The box on the top is a standard UITextView, for which I've set the attributedText. The box on the bottom is the one that has been implemented using the above steps. The same attributed string has been set for both the textViews.

Again, if there is a better approach than the one that I've used, please do let me know! :D

Hope this helps the community. :)

Cheers!



来源:https://stackoverflow.com/questions/16362407/nsattributedstring-background-color-and-rounded-corners

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!