Micro channel application number (actually called micro channel small program) today internal test, it seems that I do not matter. Continuing to pit, CoreText pit 1 implements the basic steps of CoreText and the drawing of the delete line. The main realization of this article is to draw background color, automatic identification of links, click links to jump, text and text mix.

1. Background color filling

Let’s start with an easy one. TULabel drew the strikeout line in the previous article, so fill the background color with the same step. The first step is to identify the background color style as well as the stripper style, so add judgment code to the drawRun function

Func drawRun(run: CTRun, Attributes: NSDictionary, context: CGContext) {if nil! = the attributes [NSStrikethroughStyleAttributeName] {/ / delete line CTRunDraw (run, context, CFRangeMake (0, 0)) drawStrikethroughStyle(run, attributes: attributes, context: context) } else if nil ! = the attributes [NSBackgroundColorAttributeName] {/ / background color fillBackgroundColor (run, the attributes: attributes, the context: context) CTRunDraw(run, context, CFRangeMake(0, 0)) } else { CTRunDraw(run, context, CFRangeMake(0, 0)) } }Copy the code

Note that the CTRunDraw call needs to come after the fill color.

And then how do I fill the background color

// Func fillBackgroundColor(run: CTRun, Attributes: NSDictionary, context: CGContext) {/ / for setting the background color of the let backgroundColor = attributes [NSBackgroundColorAttributeName] guard let color = BackgroundColor else {return} // Get the start of the line, // Get the width of Run, ascent, descent var ascent = CGFloat(), descent var ascent = CGFloat(), descent = CGFloat(), leading = CGFloat() let typographicWidth = CGFloat(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, // Let rect = CGRectMake(origine.x + pt.x, pt.y + origin.y - descent, typographicWidth, Ascent + descent let Components = CGColorGetComponents(color.cgcolor) CGContextSetRGBFillColor(context, components[0], components[1], components[2], components[3]) CGContextFillRect(context, rect) }Copy the code

It is used in the same way as the system Label

Private var detectLinkList: [NSTextCheckingResult]? Func detectLinks() {guard let text = self.attributedText else {return} NSDataDetector(types: NSTextCheckingType. Link. RawValue) / / will match the type of stored in an array of the let the content = text. The string self. DetectLinkList = linkDetector.matchesInString(content, options: NSMatchingOptions.ReportProgress, range: NSMakeRange(0, content.characters.count)) }Copy the code

This completes the filling of the background color and gives the following effect

Automatic link recognition

CoreText doesn’t automatically recognize links in rich text, so we need to recognize them ourselves. So how do you identify links

Private var detectLinkList: [NSTextCheckingResult]? Func detectLinks() {guard let text = self.attributedText else {return} NSDataDetector(types: NSTextCheckingType. Link. RawValue) / / will match the type of stored in an array of the let the content = text. The string self. DetectLinkList = linkDetector.matchesInString(content, options: NSMatchingOptions.ReportProgress, range: NSMakeRange(0, content.characters.count)) }Copy the code

The link has been identified, the link style we usually see needs to be different from normal text, so we have to add style to the link to distinguish

Var linkColor = uicolor.bluecolor () var linkColor = uicolor.bluecolor () NSAttributedString? , links: [NSTextCheckingResult]?) -> NSAttributedString? { guard let linkList = links else { return attributedText } guard let text = attributedText else { return attributedText } / / traverse the list of links, increase the specified style let attrText = NSMutableAttributedString (attributedString: text) linkList.forEach { [unowned self] result in attrText.addAttributes([NSForegroundColorAttributeName: self.linkColor, NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue, NSUnderlineColorAttributeName: self.linkColor], range: result.range) } return attrText }Copy the code

All that’s left is to call these two functions

Var autoDetectLinks = false override func drawRect(rect: CGRect) {if self.autodetectLinks {// detectLinks() // Add style to link self.attributedText = addLinkStyle(self.attributedText, links: self.detectLinkList) } ... }Copy the code

External calls only need to turn on automatic identification, the effect is as follows

3. Link jump

In order for a link to jump, you need to identify whether the click is a link before jumping. So let’s see how do WE get the click coordinates

override func touchesBegan(touches: Set, withEvent event: UIEvent?) { if self.autoDetectLinks { let touch: UITouch = touches.first! Let point = touch.locationInView(self) // Get the position of the rich text corresponding to the click position let index = attributedIndexAtPoint(point foundLink = linkAtIndex(index) if nil ! FoundLink {guard let link = foundLink. Link else {return} // Throw a callback if let touchLink = foundLink self.touchLinkCallback { touchLink(link: link) } } }Copy the code

Rewrite the touchesBegan function to get the click coordinates and get the corresponding rich text index based on the coordinates

private var ctframe: CTFrame? Func attributedIndexAtPoint(point: CFIndex {// remember CTFrame, Guard let frame = self. ctFrame else {return-1} let lines = CTFrameGetLines(frame Number = CFArrayGetCount(lines) Var lineorigin = [CGPoint](count: numberOfLines, repeatedValue: CGPointZero) CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), & lineOrigins) / / coordinate transform let transform = CGAffineTransformScale (CGAffineTransformMakeTranslation (0, self.bounds. size.height), 1, -1); for index in 0..Copy the code

This function CTLineGetStringIndexForPosition is the core, access to the index can according to the index after is to find the current location click the link

Func linkAtIndex(index: CFIndex) -> (foundLink: NSTextCheckingResult? , link: String?) { if self.autoDetectLinks { guard let links = self.detectLinkList else { return (nil, nil) } var foundLink: NSTextCheckingResult? var link: Links. forEach({result in if NSLocationInRange(index, result.range) { foundLink = result link = self.attributedText! .attributedSubstringFromRange(result.range).string return } }) return (foundLink, link) } return (nil, nil) }Copy the code

But if you don't want the link to appear directly in the text, but with a specific text instead of the link, but still want to be able to display, also need to be able to click, how to achieve that?

Get click index or the above function attributedIndexAtPoint, mainly change to find a specific text to add style, the actual source code see the attached link at the end of the article, here directly on the effect.

This is what happens when you click on it, and the blue underlined ones are the links

4. Text and text mixed arrangement

CoreText does something for inserting an image into the text, so we actually calculate the layout of the image by calling a callback from the CTRunDelegateCallbacks class, treating the image as a Run as well.

Let's start by defining a class that represents some information about an image

Func linkAtIndex(index: CFIndex) -> (foundLink: NSTextCheckingResult? , link: String?) { if self.autoDetectLinks { guard let links = self.detectLinkList else { return (nil, nil) } var foundLink: NSTextCheckingResult? var link: Links. forEach({result in if NSLocationInRange(index, result.range) { foundLink = result link = self.attributedText! .attributedSubstringFromRange(result.range).string return } }) return (foundLink, link) } return (nil, nil) }Copy the code

And then we need to use this class when we use it

Func linkAtIndex(index: CFIndex) -> (foundLink: NSTextCheckingResult? , link: String?) { if self.autoDetectLinks { guard let links = self.detectLinkList else { return (nil, nil) } var foundLink: NSTextCheckingResult? var link: Links. forEach({result in if NSLocationInRange(index, result.range) { foundLink = result link = self.attributedText! .attributedSubstringFromRange(result.range).string return } }) return (foundLink, link) } return (nil, nil) }Copy the code

At this point, we haven't started implementing TULabel's drawing images, so let's look at that. Check to see if any image attachments are inserted, and if so, add a RunDelegate to each image attachment to hold bits

Func linkAtIndex(index: CFIndex) -> (foundLink: NSTextCheckingResult? , link: String?) { if self.autoDetectLinks { guard let links = self.detectLinkList else { return (nil, nil) } var foundLink: NSTextCheckingResult? var link: Links. forEach({result in if NSLocationInRange(index, result.range) { foundLink = result link = self.attributedText! .attributedSubstringFromRange(result.range).string return } }) return (foundLink, link) } return (nil, nil) }Copy the code

Insert the RunDelegate method

Func imageAttribute(attachment: TUImageAttachment) -> NSAttributedString {define RunDelegateCallback and implement var imageCallback = CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: { pointer in pointer.dealloc(1) }, getAscent: { pointer -> CGFloat in return UnsafePointer(pointer).memory.size.height / 2 }, getDescent: { pointer -> CGFloat in return UnsafePointer(pointer).memory.size.height / 2 }, getWidth: {pointer -> CGFloat in return UnsafePointer(pointer).memory.sie.width}) The incoming image data in the callback let pointer = UnsafeMutablePointer. Alloc (1) pointer. The initialize (attachment image!) let runDelegate = CTRunDelegateCreate(&imageCallback, Pointer) / / for each of the images to create an empty string placeholder let imageAttributedString = NSMutableAttributedString (string: " ") imageAttributedString.addAttribute(kCTRunDelegateAttributeName as String, value: runDelegate! Range: NSMakeRange (0, 1)) / / attachments as attributes of the specified values imageAttributedString. AddAttribute (TUImageAttachmentAttributeName, value: attachment, range: NSMakeRange(0, 1)) return imageAttributedString }Copy the code

At this point, it's just a hole for the image, so this call should come before the drawRect method draws

override func drawRect(rect: CGRect) {
        ...
        if let attributedString = checkImage(self.attributedText) {
            self.attributedText = attributedString
        }
        ...
}Copy the code

Now that the pit is occupied, it's time to draw the picture

Func drawImage(run: CTRun, Attributes: NSDictionary, context: CGContext) {/ / access attributes of the corresponding image attachments let imageAttachment = attributes [TUImageAttachmentAttributeName] guard let attachment = ImageAttachment else {return} let Origin = getRunOrigin(run) var ascent = CGFloat(), descent = CGFloat(), leading = CGFloat() let typographicWidth = CGFloat(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, &leading)) let pt = CGContextGetTextPosition(context) var rect = CGRect(x: origin.x + pt.x, y: pt.y + origin.y - descent, width: typographicWidth, height: ascent + descent) let image = (attachment as! TUImageAttachment).image rect.size = image! .size // CGContextDrawImage(context, rect, image! .CGImage!) }Copy the code

After drawing, add the method to draw the image in the drawRun function

Func drawRun(run: CTRun, Attributes: NSDictionary, context: CGContext) {if nil! = the attributes [NSStrikethroughStyleAttributeName] {/ / delete line CTRunDraw (run, context, CFRangeMake (0, 0)) drawStrikethroughStyle(run, attributes: attributes, context: context) } else if nil ! = the attributes [NSBackgroundColorAttributeName] {/ / background color fillBackgroundColor (run, the attributes: attributes, the context: context) CTRunDraw(run, context, CFRangeMake(0, 0)) } else if nil ! = the attributes [TUImageAttachmentAttributeName] {/ / draw pictures drawImage (run, the attributes: attributes, the context: context) } else { CTRunDraw(run, context, CFRangeMake(0, 0)) } }Copy the code

The drawing is done. Let's see what it looks like

So far, we have completed the four styles of strikeout, background color, link and image blending. Because the middle of the iPhone7 release, with iOS10, Swift3.0, Xcode8, so naturally moved to the new position. This article is still written using Swift2.3, but another project has been opened to adapt Swift3.0. Swift2.x source code, Swift3.0 source code, please help yourself.

Words should go to study the micro channel small program, or out!

Reference: CoreText Basic Concepts CoreText Introduction Nimbus

This article by Xiao Han original, reproduced please indicate the source!!