copyright

Original article, reprint please retain the following information:

This chapter, the preface

Using CoreText technology, we can do complex typesetting of rich text. With some simple extensions, we can also click on images and links. Compared with UIWebView, CoreText technology has the advantages of less memory and can be rendered in the background, so it is very suitable for content typesetting.

In this chapter, we’ll start with the basics and work our way through a typography engine that supports text mixing, images, and link clicks.

CoreText profile

CoreText is the underlying technology for processing text and fonts. It works directly with Core Graphics (also known as Quartz). Quartz is a 2D graphics rendering engine capable of handling graphical displays in OSX and iOS.

Quartz works directly with fonts (font) and glyphs, rendering text to the interface, and is the only module in the base library capable of handling glyphs. Therefore, CoreText needs to pass the displayed text content, position, font, and glyphs directly to Quartz for typography purposes. Compared to other UI components, CoreText provides faster typography because it interacts directly with Quartz.

CoreText is very low-level, and the top UI controls (including UILabel, UITextField and UITextView) and UIWebView are all based on CoreText.

Note: This is a post-ios7 architecture diagram. Before iOS7, there was no Text Kit class in this diagram, but CoreText is still the module at the bottom that directly deals with Core Graphics.

UIWebView is also an alternative for handling complex text typesetting. For typography, coreText-based versus UIWebView-based has the following advantages:

  • CoreText takes up less memory and renders faster, UIWebView takes up more memory and renders slower.
  • CoreText gets the exact height of the content in front of the rendering world (as long as you have CTFrame), whereas UIWebView doesn’t get the height of the content until it’s rendered (and you need javascript code to get it)
  • CoreText CTFrame can be rendered in the background thread, UIWebView content can only be rendered in the main thread (UI thread).
  • Based on CoreText can do better native interaction effect, interaction effect can be more delicate. UIWebView’s interaction effects are implemented in javascript, and there will be some lag in the interaction effect. For example, in UIWebView, a simple button pressing effect cannot achieve the real-time and delicate pressing effect of the native button.

Of course, coreText-based typography has some disadvantages:

  • CoreText rendered content does not support content copying as easily as UIWebView does.
  • CoreText based typesetting needs to deal with a lot of complex logic, such as the need to deal with the image and text mixing related logic, but also need to achieve their own link click operation support.

In the industry, many applications have adopted the typesetting scheme based on CoreText technology, such as Sina Weibo client and Duojian Reading client. Ape Question Bank, a startup company I work for, also uses its own typesetting engine based on CoreText technology. The following picture is a mixed text interface of our product (all formulas are presented in the form of pictures). It can be seen that the typesetting effect of pictures and words is very good.

Basic typesetting engine based on CoreText

A typography engine without images

Let’s try to complete a coreText-based typesetting engine. We’ll start with simple typography and work our way up to blending text and text, linking and clicking.

First let’s try to build a text-only typesetting engine that doesn’t support graphic content.

Note 1: Due to the large amount of code in the whole layout engine, only the most critical core code is listed in this article for the convenience of readers. For the complete code, please refer to the corresponding Github project of this book. The project address is github.com/tangqiaoboy… .

Can output Hello World CoreText project

steps

Let’s start with a new Xcode project. The steps are as follows:

  1. Open Xcode, select “File” – > “New” – > “Project”, in the dialog box that pops up, select “Single View Application”, then click “Next”. (figure 2)
  2. Next fill in the project name CoreTextDemo and click “Next”. (figure 3)
  3. After selecting the save directory, we have successfully created an empty project.

Figure 2

Figure 3

Right click on the project directory “CoreTextDemo”, select “New File”, then fill in the class name CTDisplayView and make its parent UIView. (As shown below)

Next, we have the import header CoreText/ coreText. h in the ctDisplayView. m file, and type the following code to implement the drawRect method:

#import "CTDisplayView.h" #import "CoreText/CoreText.h" @implementation CTDisplayView - (void)drawRect:(CGRect)rect { [super drawRect:rect]; / / step 1 CGContextRef context = UIGraphicsGetCurrentContext (); / / step 2 CGContextSetTextMatrix (context, CGAffineTransformIdentity); CGContextTranslateCTM(context, 0, self.bounds.size.height); CGContextScaleCTM (context, 1.0, 1.0); // Step 3 CGMutablePathRef path = CGPathCreateMutable(); CGPathAddRect(path, NULL, self.bounds); NSAttributedString *attString = [[NSAttributedString alloc] initWithString:@"Hello World!"] ; CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attString); CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [attString length]), path, NULL); // Step 5 CTFrameDraw(frame, context); // Step 6 CFRelease(frame); CFRelease(path); CFRelease(framesetter); } @endCopy the code

Open the program’s Storyboard file main_iphone.storyboard: Perform the following 2 steps:

  1. Drag a UIView control to the center of the main screen. (Step 1 as shown below)
  2. Change the class name of the UIView control fromUIViewModified toCTDisplayView. (Step 2 as shown below)

And then we run the program, and we see Hello World in the middle of the program. The diagram below.

Code interpretation

Here are the main steps of the drawRect method:

  1. Gets the context in which the canvas is currently drawn for subsequent content to be drawn on the canvas.
  2. Flip the coordinate system up and down. For the underlying drawing engine, the bottom left corner of the screen is the (0, 0) coordinate. For the top UIKit, the top left corner is the (0, 0) coordinate. So let’s do UIKit for the frame description, so let’s do a frame flip up and down here. When you flip it, the bottom and the top (0, 0) coordinates are coincident.

    Let’s comment out this block of code to make sense of it, and you’ll see that the entire Hello World screen flips up and down, as shown in the image below.

  3. Create a drawing area, CoreText itself supports various text typesetting areas, so we’re simply going to use the entire UIView as the typesetting area.

To deepen our understanding, let’s replace the code for this step with the following code to test the interface changes caused by setting different drawing areas.

// Step 3 CGMutablePathRef path = CGPathCreateMutable(); CGPathAddEllipseInRect(path, NULL, self.bounds); // Step 4 NSAttributedString *attString = [[NSAttributedString alloc] initWithString:@"Hello World! "Create a drawing area, CoreText itself supports various text typesetting areas," "we're simply going to use the entire UIView interface as the typesetting area. "" For better understanding, we recommend that the reader replace the code for this step with the following code," "To test the interface changes caused by setting different drawing areas. "] ;Copy the code

The execution result is as follows:

Code basic macro definition and Category

To make our coding easier, I added the following basic macro definitions to the coreTextDemo-prefix. PCH file to make it easier to use NSLog and UIColor.

#ifdef DEBUG
#define debugLog(...) NSLog(__VA_ARGS__)
#define debugMethod() NSLog(@"%s", __func__)
#else
#define debugLog(...)
#define debugMethod()
#endif

#define RGB(A, B, C)    [UIColor colorWithRed:A/255.0 green:B/255.0 blue:C/255.0 alpha:1.0]
Copy the code

I’ve also added some extensions to UIView frame adjustments that make it easy to adjust UIView’s x, y, width, and height equivalency. Some of the key codes are as follows (see the sample project for the complete code) :

UIView + frameAdjust. H file:

#import 

@interface UIView (frameAdjust)

- (CGFloat)x;
- (void)setX:(CGFloat)x;

- (CGFloat)y;
- (void)setY:(CGFloat)y;

- (CGFloat)height;
- (void)setHeight:(CGFloat)height;

- (CGFloat)width;
- (void)setWidth:(CGFloat)width;

@endCopy the code

UIView + frameAdjust. M file:

@implementation UIView (frameAdjust)
- (CGFloat)x {
    return self.frame.origin.x;
}

- (void)setX:(CGFloat)x {
    self.frame = CGRectMake(x, self.y, self.width, self.height);
}

- (CGFloat)y {
    return self.frame.origin.y;
}

- (void)setY:(CGFloat)y {
    self.frame = CGRectMake(self.x, y, self.width, self.height);
}

- (CGFloat)height {
    return self.frame.size.height;
}
- (void)setHeight:(CGFloat)height {
    self.frame = CGRectMake(self.x, self.y, self.width, height);
}

- (CGFloat)width {
    return self.frame.size.width;
}
- (void)setWidth:(CGFloat)width {
    self.frame = CGRectMake(self.x, self.y, width, self.height);
}

@endCopy the code

The rest of the code in this article defaults to #import the macro definition and UIView Category mentioned above.

Layout Engine Framework

The Hello World project above only demonstrates the basic capabilities of Core Text typography. But to make a decent typography engine, we can’t simply put all the code into the drawRect method of the CTDisplayView. According to the “Single responsibility principle” of design patterns, we should break up functionality, putting different functionality into separate classes.

For a complex typography engine, its functions can be broken down into the following classes:

  1. A display class that displays content, not typesetting
  2. A model class that holds all the data needed for the display
  3. A typesetting class, used to implement text content typesetting
  4. A configuration class that implements configurable items for typesetting

Note: “Single Responsibility Principle” reference link: zh.wikipedia.org/wiki/%E5%8D…

According to the above principles, part of the content in CTDisplayView is divided into four classes:

  1. CTFrameParserConfigClass to configure parameters for drawing, such as text color, size, line spacing, etc.
  2. CTFrameParserClass to generate what is needed to eventually draw the interfaceCTFrameRefInstance.
  3. CoreTextDataClass used to hold data fromCTFrameParserThe class generationCTFrameRefInstances andCTFrameRefThe actual height required to draw.
  4. CTDisplayViewClass, holdCoreTextDataClass that is responsible for turning theCTFrameRefDraw to the interface.

The key code for these four classes is as follows:

CTFrameParserConfig class:

#import 
@interface CTFrameParserConfig : NSObject

@property (nonatomic, assign) CGFloat width;
@property (nonatomic, assign) CGFloat fontSize;
@property (nonatomic, assign) CGFloat lineSpace;
@property (nonatomic, strong) UIColor *textColor;

@endCopy the code

#import "CTFrameParserConfig.h" @implementation CTFrameParserConfig - (id)init { self = [super init]; If (self) {_width = 200.0f; _fontSize = 16.0 f; _lineSpace = 8.0 f; _textColor = RGB(108, 108, 108); } return self; } @endCopy the code

CTFrameParser class:

#import 
#import "CoreTextData.h"
#import "CTFrameParserConfig.h"

@interface CTFrameParser : NSObject

+ (CoreTextData *)parseContent:(NSString *)content config:(CTFrameParserConfig*)config;

@endCopy the code

#import "CTFrameParser.h" #import "CTFrameParserConfig.h" @implementation CTFrameParser + (NSDictionary *)attributesWithConfig:(CTFrameParserConfig *)config { CGFloat fontSize = config.fontSize; CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL); CGFloat lineSpacing = config.lineSpace; const CFIndex kNumberOfSettings = 3; CTParagraphStyleSetting theSettings[kNumberOfSettings] = { { kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof(CGFloat), &lineSpacing }, { kCTParagraphStyleSpecifierMaximumLineSpacing, sizeof(CGFloat), &lineSpacing }, { kCTParagraphStyleSpecifierMinimumLineSpacing, sizeof(CGFloat), &lineSpacing } }; CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, kNumberOfSettings); UIColor * textColor = config.textColor; NSMutableDictionary * dict = [NSMutableDictionary dictionary]; dict[(id)kCTForegroundColorAttributeName] = (id)textColor.CGColor; dict[(id)kCTFontAttributeName] = (__bridge id)fontRef; dict[(id)kCTParagraphStyleAttributeName] = (__bridge id)theParagraphRef; CFRelease(theParagraphRef); CFRelease(fontRef); return dict; } + (CoreTextData *)parseContent:(NSString *)content config:(CTFrameParserConfig*)config { NSDictionary *attributes = [self attributesWithConfig:config]; NSAttributedString *contentString = [[NSAttributedString alloc] initWithString:content attributes:attributes]; // Create CTFramesetterRef instance CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)contentString); // Get the height of the region to draw CGSize restrictSize = CGSizeMake(config.width, CGFLOAT_MAX); CGSize coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints (framesetter, CFRangeMake (0, 0), nil, restrictSize, nil); CGFloat textHeight = coreTextSize.height; / / generated CTFrameRef instance CTFrameRef frame = [self createFrameWithFramesetter: framesetter config: config height: textHeight]; // Save the generated CTFrameRef instance and the calculated draw height to the CoreTextData instance, CoreTextData *data = [[CoreTextData alloc] init]; data.ctFrame = frame; data.height = textHeight; // Release memory CFRelease(frame); CFRelease(framesetter); return data; } + (CTFrameRef)createFrameWithFramesetter:(CTFramesetterRef)framesetter config:(CTFrameParserConfig *)config height:(CGFloat)height { CGMutablePathRef path = CGPathCreateMutable(); CGPathAddRect(path, NULL, CGRectMake(0, 0, config.width, height)); CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL); CFRelease(path); return frame; } @endCopy the code

CoreTextData class:

#import 

@interface CoreTextData : NSObject

@property (assign, nonatomic) CTFrameRef ctFrame;
@property (assign, nonatomic) CGFloat height;

@endCopy the code

#import "CoreTextData.h" @implementation CoreTextData - (void)setCtFrame:(CTFrameRef)ctFrame { if (_ctFrame ! = ctFrame) { if (_ctFrame ! = nil) { CFRelease(_ctFrame); } CFRetain(ctFrame); _ctFrame = ctFrame; } } - (void)dealloc { if (_ctFrame ! = nil) { CFRelease(_ctFrame); _ctFrame = nil; } } @endCopy the code

CTDisplayView class:

#import 
#import "CoreTextData.h"

@interface CTDisplayView : UIView

@property (strong, nonatomic) CoreTextData * data;

@endCopy the code

#import "CTDisplayView.h"

@implementation CTDisplayView

- (void)drawRect:(CGRect)rect
{
    [super drawRect:rect];
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
    CGContextTranslateCTM(context, 0, self.bounds.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);

    if (self.data) {
        CTFrameDraw(self.data.ctFrame, context);
    }
}

@endCopy the code

The logic in the above four classes is basically the same as that in the Hello World project, except that it is divided into four classes. In addition, CTFrameParser adds methods to get the height of the region to be drawn and saves the height information to an instance of the CoreTextData class. The reason for obtaining the height of the drawing area is that in many practical scenarios, we need to know the height of the content to be displayed before we can draw.

For example, when a UITableView is rendering, the UITableView first calls the delegate method to get the height of each cell to be rendered:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;Copy the code

And then UITableView is going to calculate exactly what UITableView cells need to be drawn at the current scrolling position, and then for those cells that need to be drawn, The UITableView then calls back to its data source the following method to get the instance of the UITableViewCell:

- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath;Copy the code

In the above case, if we use CoreText as the contents of the TableViewCell, we must know the height of each Cell before it is drawn, otherwise UITableView will not work properly.

After completing the above four classes, we can simply add the following code to the viewController.m file to configure the CTDisplayView to display contents, position, height, font, color, etc. The code is shown below.

#import "ViewController.h" @interface ViewController () @property (weak, nonatomic) IBOutlet CTDisplayView *ctView; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; CTFrameParserConfig *config = [[CTFrameParserConfig alloc] init]; config.textColor = [UIColor redColor]; config.width = self.ctView.width; CoreTextData *data = [CTFrameParser parseContent:@" config:config]; self.ctView.data = data; self.ctView.height = data.height; self.ctView.backgroundColor = [UIColor yellowColor]; } @endCopy the code

Note: Since Xcode4.0, the default interface editing has enabled the Use of Use Autolayout, but since we have directly modified the frame information of the variable ctView in the code, So you need to uncheck Use Autolayout in main_iphone.storyboard. As shown below:

The following is the UML diagram of the framework. From the diagram, we can see that the relationship of the four Core Text classes is as follows:

  1. CTFrameParserthroughCTFrameparserConfigInstance to generateCoreTextDataInstance.
  2. CTDisplayViewBy holdingCoreTextDataExample to get all the information needed to draw.
  3. ViewControllerClass through configurationCTFrameparserConfigInstance, and get the generatedCoreTextDataInstance, and finally assign it to hisCTDisplayViewMember to display the specified content on the interface.

Git checkout basic_arch (git checkout basic_arch, git checkout basic_arch, git checkout basic_arch)

Note 2: To make it easier to manipulate the frame property of UIView, a file named UIView+ frameadjust.m is added to the project, which adds a method to set the height property of UIView directly by Category.

Customize typesetting file formats

For the example above, we added a method to CTFrameParser to convert NSString to CoreTextData. This approach has many limitations, because while font size, color, and line height can be customized for the entire content, it cannot be customized for any part of the content. For example, if we only want the first three words of the content to appear in red and the rest of the text to appear in black, we can’t do this.

The solution is simple. We make CTFrameParser support accept NSAttributeString as an argument, and then set the desired NSAttributeString information in the ViewController class.

@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; CTFrameParserConfig *config = [[CTFrameParserConfig alloc] init]; config.width = self.ctView.width; config.textColor = [UIColor blackColor]; NSString * Content = @" For the above example, we added a method to CTFrameParser that converts NSString to CoreTextData." This approach has many limitations, because while you can customize font size, color, and line height for the entire content, you can't customize any part of the content. "For example, if we only want the first three words of the content to appear in red and the rest of the text to appear in black, we can't do that." "\n\n" "The solution is very simple, we make CTFrameParser support accept NSAttributeString as an argument, and then set the NSAttributeString to the information we want." ; NSDictionary *attr = [CTFrameParser attributesWithConfig:config]; NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:content attributes:attr]; [attributedString addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, 7)]; CoreTextData *data = [CTFrameParser parseAttributedContent:attributedString config:config]; self.ctView.data = data; self.ctView.height = data.height; self.ctView.backgroundColor = [UIColor yellowColor]; } @endCopy the code

The result is shown below, where we conveniently changed the first seven characters to red.

Further, in practice, we would like to use a typesetting file to set the content, color, font size and other information of the text to be typesetted. When I was developing the ape question bank application, I defined a typesetting template based on UBB by myself, but it would take a lot of space to implement the parser of the typesetting file. Considering that this is not the focus of this chapter, we will explain the idea with a relatively simple typesetting file.

We specify that the template file for typesetting should be in JSON format. JSON(JavaScript Object Notation) is a lightweight data interchange format that is easy to read and write as well as machine parse and generation. Starting with 5.0, iOS provides a library called NSJSONSerialization to help developers parse JSON. Prior to iOS5.0, there were several open source libraries for JSON parsing, such as JSONKit.

Our sample typesetting template file looks like this:

[{"color" : "blue", "content" : "color", "size" : 16, "type" : "TXT"}, {"color" : "Red", "content" : "content, color, font," "size" : 22, "type" : "TXT"}, {" color ":" black ", "content" : "the information such as size. \ n", "size" : 16, "type" : "txt" }, { "color" : "default", "content" : "When I was developing the question bank application, I defined a typesetting template based on UBB by myself, but it takes a lot of space to implement the parser of the typesetting file. Considering that this is not the focus of this chapter, we will explain the idea with a simpler typesetting file. ", "type" :" TXT "}]Copy the code

Using the NSJSONSerialization class provided by Apple, we can convert the template file above into an NSArray array, where each array element is an NSDictionary that represents a paragraph of text with the same Settings. For simplicity, our configuration file supports only color and font size, but the reader can easily add other configuration information along the same lines.

Next we will add a method to CTFrameParser that will generate CoreTextData from the template file in the format above. Finally, our implementation code is as follows:

// method one + (CoreTextData *)parseTemplateFile:(NSString *)path config:(CTFrameParserConfig*)config {NSAttributedString *content = [self loadTemplateFile:path config:config]; return [self parseAttributedContent:content config:config]; } // method 2 + (NSAttributedString *)loadTemplateFile:(NSString *)path config:(CTFrameParserConfig*)config {NSData *data = [NSData dataWithContentsOfFile:path]; NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; if (data) { NSArray *array = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil]; if ([array isKindOfClass:[NSArray class]]) { for (NSDictionary *dict in array) { NSString *type = dict[@"type"]; if ([type isEqualToString:@"txt"]) { NSAttributedString *as = [self parseAttributedContentFromNSDictionary:dict config:config]; [result appendAttributedString:as]; } } } } return result; } / / method 3 + (NSAttributedString *) dict parseAttributedContentFromNSDictionary: (NSDictionary *) config:(CTFrameParserConfig*)config { NSMutableDictionary *attributes = [self attributesWithConfig:config]; // set color UIColor *color = [self colorFromTemplate:dict[@"color"]]; if (color) { attributes[(id)kCTForegroundColorAttributeName] = (id)color.CGColor; } // set font size CGFloat fontSize = [dict[@"size"] floatValue]; if (fontSize > 0) { CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL); attributes[(id)kCTFontAttributeName] = (__bridge id)fontRef; CFRelease(fontRef); } NSString *content = dict[@"content"]; return [[NSAttributedString alloc] initWithString:content attributes:attributes]; } // colorFromTemplate:(NSString *)name {if ([name isEqualToString:@"blue"]) {return [UIColor blueColor]; } else if ([name isEqualToString:@"red"]) { return [UIColor redColor]; } else if ([name isEqualToString:@"black"]) { return [UIColor blackColor]; } else { return nil; (CoreTextData *)parseAttributedContent:(NSAttributedString *)content config:(CTFrameParserConfig*)config { // Create CTFramesetterRef instance CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)content); // Get the height of the region to be cached CGSize restrictSize = CGSizeMake(config.width, CGFLOAT_MAX); CGSize coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints (framesetter, CFRangeMake (0, 0), nil, restrictSize, nil); CGFloat textHeight = coreTextSize.height; / / generated CTFrameRef instance CTFrameRef frame = [self createFrameWithFramesetter: framesetter config: config height: textHeight]; // Save the generated CTFrameRef instance and the calculated cache height to the CoreTextData instance. CoreTextData *data = [[CoreTextData alloc] init]; data.ctFrame = frame; data.height = textHeight; // Release memory CFRelease(frame); CFRelease(framesetter); return data; } / / method 6 + (CTFrameRef) createFrameWithFramesetter: CTFramesetterRef framesetter config (config CTFrameParserConfig *) height:(CGFloat)height { CGMutablePathRef path = CGPathCreateMutable(); CGPathAddRect(path, NULL, CGRectMake(0, 0, config.width, height)); CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL); CFRelease(path); return frame; }Copy the code

The code above consists of six submethods:

  • Method 1 provides an external interface, method 2 reads the content from a JSON template file, and method 5 generates itCoreTextData.
  • Method two reads the contents of the JSON file and calls method three to get the data fromNSDictionarytoNSAttributedStringThe conversion result of.
  • Methods threeNSDictionaryContent conversion toNSAttributedString.
  • Method four provides the followingNSStringtoUIColorThe function.
  • Method five: Accept oneNSAttributedStringAnd aconfigParameters,NSAttributedStringConverted toCoreTextDataTo return.
  • Method six is an auxiliary function of method five, called by method five.

Then we make a change to the calling code in the ViewController to load from the template file, as follows:

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    CTFrameParserConfig *config = [[CTFrameParserConfig alloc] init];
    config.width = self.ctView.width;
    NSString *path = [[NSBundle mainBundle] pathForResource:@"content" ofType:@"json"];
    CoreTextData *data = [CTFrameParser parseTemplateFile:path config:config];
    self.ctView.data = data;
    self.ctView.height = data.height;
    self.ctView.backgroundColor = [UIColor whiteColor];
}

@endCopy the code

The result of the final run is shown below, as you can see, with a simple template file, we can easily define the layout configuration information.

You can use git Checkout json_template in your sample project to see sample code that can be run.