preface

What would you do if you had one of these requirements, several UILabel + UIImageView? NSAttributedString joining together? CoreText?

I believe that either way the code is large and hard to reuse, other languages are so easy to write rich text, Android naturally support simple HTML, RN(JS) tags and tags, but anyone who has used rich text in iOS will find it difficult to use… At present, the industry is powerful, relatively easy to use YYText, but the design idea is as much as possible with UILabel, UITextView similar, so the relative use is not particularly simple, and the framework is heavy. Based on the current situation, a lightweight framework has been developed to meet most of the rich text needs, and provides gesture response, draw callback, graphic alignmentation, CoreText property extension, support for network images, asynchronous drawing performance optimization, and most importantly, simple to use, easy to write a graphic mixed text through the chain syntax.

example

As shown in the figure, a picture and text mixing involves font, color, word spacing, line spacing, image alignment, text alignment, stroke and other attributes, as well as network image and local image mixing, gesture response and other requirements, using this framework can be achieved as follows:

/ /... Omitted constant declaration

TextBuild
.append(title).font(titleFont).color(titleColor).onClicked(titleOnClicked).onLayout(titleOnLayout)
.append(firstPara).color(firstParaColor).align(@0)
.append(webImage).font(separateLineFont).minLineHeight(@100)
.append(separateLine).font(separateLineFont).strokeColor(separateLineColor).strokeWidth(@1).horizontalOffset(@30)
.append(locolImage).horizontalOffset(@30)
.append(lastPara).font(lastParaFont).align(@1).maxLineHeight(@20)
.append(bookName).font(bookNameFont).color(bookNameColor).onClicked(bookOnClicked).align(@1)
.append(lineLayer).attachSize(lineLayerSize)
.append(quote).color(quoteColor).letterSpace(@0).minLineSpace(@8).align(@0)
.append(buyButton).attachSize(buyButtonSize).attachAlign(@0)
// Set the global default property with a lower priority than the specified property
.entire().maxSize(maxSize).align(@2).letterSpace(@3).minLineHeight(@20).attachAlign(@1).onClicked(textOnClicked).attachSize(attachSize).shadow(shadow).cornerRadius(@50).backgroundLayer(gradientLayer).horizontalMargin(@10).preferHeight(@(preferHeight))
/ / draw the View
.drawView(^(UIView *drawView) {
    [self.view addSubview:drawView];
});
Copy the code

In actual requirements, NSStrings can also be combined according to different conditions, and finally drawn:

/ /... Omitted constant declaration

// Splice articles
/ / title
NSString *titleString = TextBuild.append(title).font(titleFont).color(titleColor).onClicked(titleOnClicked).onLayout(titleOnLayout);
/ / the first paragraph
NSString *firstParaString = TextBuild.append(firstPara).color(firstParaColor).align(@0);
// The image should start with an empty string
NSString *webImageString = TextBuild.append(webImage).font(separateLineFont).minLineHeight(@100);
/ / line
NSString *separateLineString = TextBuild.append(separateLine).font(separateLineFont).strokeColor(separateLineColor).strokeWidth(@1).horizontalOffset(@30);
// Local images
NSString *locolImageString = TextBuild.append(locolImage).horizontalOffset(@30);
// The last paragraph
NSString *lastParaString = TextBuild.append(lastPara).font(lastParaFont).align(@1).maxLineHeight(@20);
/ / title
NSString *bookNameString = TextBuild.append(bookName).font(bookNameFont).color(bookNameColor).onClicked(bookOnClicked).align(@1).maxLineHeight(@20);
// Reference line Layer
NSString *lineLayerString = TextBuild.append(lineLayer).attachSize(lineLayerSize);
/ / reference
NSString *quoteString = TextBuild.append(quote).color(quoteColor).letterSpace(@0).minLineSpace(@8).align(@0);
/ / button
NSString *buttonString = TextBuild.append(buyButton).attachSize(buyButtonSize).attachAlign(@0);

// Set the global default property with a lower priority than the specified property
NSString *defaultAttributes = TextBuild.entire()
.maxSize(maxSize).align(@2).letterSpace(@3).minLineHeight(@20).attachAlign(@1).onClicked(textOnClicked).attachSize(attachSize).shadow(shadow).cornerRadius(@50).backgroundLayer(gradientLayer).horizontalMargin(@10).preferHeight(@(preferHeight));

/ / stitching
TextBuild
.append(titleString)
.append(firstParaString)
.append(webImageString)
.append(separateLineString)
.append(locolImageString)
.append(lastParaString)
.append(bookNameString)
.append(lineLayerString)
.append(quoteString)
.append(buttonString)
// Set the default properties
.append(defaultAttributes)
/ / draw the Layer
.drawLayer(^(CALayer *drawLayer) {
    [self.view.layer addSublayer:drawLayer];
});
Copy the code

Core methods and attributes

Extension of NSString, operation string generated to draw the corresponding view

Core method

  • append(id content)

    The concatenation content can be text (NSString), image (UIImage), image link (NSURL)(attachSize must be specified), and view (CALayer/UIView).Copy the code
  • entire()

    Set the whole rich text to take precedence over the specified attribute. The more important attribute maxSize sets the drawing constraint. Some paragraph attributes are set to take effect only within the entire paragraphCopy the code
  • drawLayer(^(CALayer *drawLayer)completion)

    Draw layer, cannot respond to gestures, when there is a UIView mixed suggest using drawViewCopy the code
  • drawView(^(UIView *drawView)completion)

    Draw a View that responds to a gesture. This API is recommendedCopy the code

attribute

General properties
  • VerticalOffset indicates the verticalOffset
  • HorizontalOffset indicates the horizontalOffset
  • OnClicked callback
  • OnLayout displays callbacks
  • CacheFrame caches where this segment of text is drawn
  • MinLineSpace Minimum line spacing
  • MaxLineSpace Maximum line spacing
  • MinLineHeight Minimum row height
  • MaxLineHeight Minimum row height
  • Align, integer, 0 is left, 1 is right, 2 is center, see CTTextAlignment
  • LineBreakMode Alignment, shaping, see NSLineBreakMode
String attribute
  • Font: Text font/image center aligned font
  • Color color
  • LetterSpace word spacing
  • StrokeWidth Width of the stroke. The integer is hollow. Negative Color in effect
  • StrokeColor strokeColor
  • VerticalForm Text is drawn according to the writing direction of the text. Default: no (0), yes (non-0)
  • Underline type, integer, 0 for none, 1 for thin line, 2 for bold, 9 for double bar reference CTUnderlineStyle
Image properties
  • AttachSize Image size. The default value is the size of the image itself, which is automatically adjusted according to the image scaling (2x 3x)
  • AttachAlign Image alignment mode (0 is the default value and the base line is aligned) 1 Center to a specified font size See ZJTextattachAlign
Paragraph attributes
Note: The following attribute takes effect only after the entire() function; If there is only one valid piece of text (non-null characters and other types), it can also take effect directly.
  • MaxSize Constraint size for drawing. Default is unlimited
  • Shadow Indicates the shadow of the text
  • PreferHeight indicates the height expected to be drawn, with the content centered
  • VerticalMargin indicates the verticalMargin. If preferHeight is set, this attribute does not take effect
  • HorizontalMargin horizontalMargin
  • BackgroundColor backgroundColor
  • BackgroundLayer Background view, commonly used image background/gradient background
  • CornerRadius rounded corners

performance

In general, CoreText + asynchronous drawing is used to complete the picture, and the theoretical performance will be relatively high. The following data is tested for your reference:

Content: a piece of text plus two pictures

Model: iPhone 6

Test results:

General (NSAttributedString + UILabel) procedure: Create -> show (draw) General analysis:

  1. Main thread code is around 28ms. (Main thread code start to end time)
  2. UILabel display (drawing) takes about 42ms (addSubview to drawRect)
  3. The overall time is about 70ms, all in the main thread

Asynchronous drawing (this framework) process: create -> asynchronous drawing -> display asynchronous drawing analysis:

  1. The main thread (creation) code is around 28ms.
  2. It takes about 84ms to create (main thread) + asynchronous drawing (main thread code starts to draw the picture callback)
  3. According to the results of 1 and 2, it takes about 56ms to draw the child thread. In addition, after several experiments (drawing large paragraphs of text), it is found that drawing complex paragraphs also consumes less time
  4. It takes about 0.75ms to display (addSubview to drawRect)
  5. The overall time is about 85ms, of which the main thread is 29ms and the child thread is 56ms

Conclusion:

  1. Compared with the conventional method, the main thread pressure is reduced by 70ms -> 29ms
  2. The more complex the text, the higher the benefit (multiple controls integrated, asynchronous drawing), the large rich text in the figure above also only 15ms more time, less time increase
  3. Overall time increased by 15ms, all in child threads, after all, processing more logic than the system.
  4. The overall performance is similar to YYText

The installation

Github

ZJAttributedText

Pod

pod 'ZJAttributedText'
Copy the code

The framework relies on SDWebImage (which almost all apps integrate and can share a set of cache logic)

The tail

Internal implementation code is not much, almost all steps have added comments, if you need to learn CoreText, asynchronous drawing, chain syntax, is a good Demo, if you are interested, you can add CoreText related content, this part of the online data are older, more errors. Welcome issue and Star ~