Implement the following effect

You can use a lot of controls,

In the red box on the left, a UILabel;

In the red box on the right, a UILabel

A bunch of UILabel codes


CoreText allows you to draw with a control

Ideas:

Let’s translate the original text into the following

Turn the second slide into the original effect


The first step is to create a rich text with a similar structure

Make sure that the Y coordinates of each row are controllable

(Ensure that the content not in the red box on the right is consistent with the final)

(Make sure that the contents of the red box on the right, the number of rows and the y-coordinate, are consistent with the final)

  • Rich text, convert to CTFrame 1, the second image appears, (remove the text in the red box on the right)

The key is to separate the rich text in the red box on the right

CTFrame 1, width = the width of the rich text in the red box on the right + 50

  • The red box on the right is rich text. Convert it to CTFrame 2 and paste it to the space left by the previous step

The text in the red box on the right cannot use CTFrame 1

A red box on the right is a CTFrame two

Given a string, how do you know how many lines it has

String, converted to rich text,

Rich text, convert to CTLine,

Get the width of CTLine lineWidth,

I know the width of the textbox containerWidth,

Int(ceil(lineWidth/containerWidth))

Implementation:

Example data

[{" string ", "world が eventually る ま で は", "type" : 6}, {" string ":" the world が eventually わ る に smell before か せ て お く れ よ ", "title" : "the lyrics:", "type" : 7}, {" string ": "Tell me before the end of the world", "title" : "note 釈 :", "type" : 8}, {" string ": "World が eventually わ る ま で は from れ る matter も な い. そ う may っ て た thousands of と. の night 戻 ら な when だ い け が somehow fai い て は", "title" : "the lyrics:", "type" : 7}, {" string ": I want to go to the end of the world. I want thousands of nights. Why do they shine only when they don't come back?Copy the code

into

Used for placeholders in the red box on the right, and rendering areas outside the red box on the right

  • Placeholders in the red box on the right

The splitting technique, it’s written earlier

SubList is split out of the content string

▿ 0:250-string: "World touch Type: 6 ▿ 1:250-string:" World Touch type: 305 "- Type: 7 set title: ▿ subList: Optional<Array<String>> ▿ subList: Optional<Array<String>> "Ha ha ha" - 1: "ha ha ha" ▿ 2: Coupling - String:" Tell me before the World Ends "- type: 102 ▿ title: Optional< string > - some: "Note 釈 :" ▿ 3: Coupling - string: "the world が eventually わ る ま で は from れ る matter も な い. そ う may っ て た thousands of と の night. Saving "- type: 7 ▿ title: Optional<String> - some:" ▿ subList: ▿ some: 3 elements - 0: "ha ha ha" - 1: "ha ha ha" - 2: "Ha ha ha" ▿ 4: Coupling-string:" I'm going to the end of the world. I hope thousands of nights. Why only shine when they don't come back "- type: 8 ▿ title: Optional<String> - some:" Note Hakuten: "▿ subList: ▿ some: 3 elements - 0: "ha ha ha" - 1: "ha ha ha" - 2: "ha ha ha"Copy the code

And red box inside draw information

Struct Renderer{// in the red box, let paragraph: [ParagraphRenderer]} struct ParagraphRenderer{// Corresponds to the actual drawing of the starting line let lineIdx: Int // The red box on the right corresponds to the text let Content: String // The number of lines corresponding to the red box on the right let CNT: Int // the text corresponding to the left box let t: String // Let beBlack: Var lines = [CTLine]()}Copy the code

See github Repo at the bottom for the model calculation section

For calculation ideas, refer to previous blogs at the bottom

draw

// Core Text coordinates, the origin is in the lower left, // first flip UIKit coordinates, // lastY, is the row spacing of the total var lastY: CGFloat = 0 var index = 0 let limit = info.text. count var startIndex: Int? = nil for (i,line) in lines.enumerated(){ var lineAscent:CGFloat = 0 var lineDescent:CGFloat = 0 var lineLeading:CGFloat  = 0 CTLineGetTypographicBounds(line , &lineAscent, &lineDescent, &lineLeading) var lineOrigin = originsArray[i] lineOrigin.x = TextContentConst.padding + lineOrigin.x // ... // let yOffset = lineOrigin. Y-linedescent -20 if I == info.lineindex {ctx.draw(line: YOffset)} ctx.textPosition = lineOrigin // Draw the record, // If the contents of the red box are drawn, Var toDraw = false var toDraw = false if let f = startIndex{if I >= f{ Let biscuit = info.paragraph[index-1] ctx.textPosition = CGPoint(x: lineOrigin.x + TextContentConst.indentSecond, y: Y) // startIndex = I + biscuit. CNT time, // biscuit. CNT + I -f = initial line // biscuit. CTLineDraw(biscuit. Lines [biscuit. CNT - (f - I)], CTX) toDraw = true}} If index < limit, Info.paragraph [index]. LineIdx == I {let biscuit = info.paragraph[index] NSAttributedString = biscuit.t.seven(toBreak: False) let ln = CTLineCreateWithAttributedString (attributedStr) / / picture on the left side of the red box CTLineDraw (ln, ctx) ctx.textPosition = CGPoint(x: lineOrigin.x + TextContentConst.indentSecond, y: Y) // Draw the first line of the red box on the right CTLineDraw(biscuit. Lines [0], ctx) toDraw = true startIndex = i + biscuit.cnt index += 1 } if toDraw == false{ CTLineDraw(line, ctx) } }Copy the code
Model optimization
Case 7: // Just a mark //... let attributedStr = NSAttributedString() let lnTwo = CTLineCreateWithAttributedString(attributedStr) let w = lnTwo.lnSize.width let cnt = w / TextContentConst.widthBritain var m = model let len = Int(ceil(cnt)) if len > 1{ // The right red box is more than one line long and placed in Frame 2 (1.. <len).forEach { acht.append($0 + enLineIdx) } m.subList = [String](repeating: product, count: len) infoEn.append(ParagraphRenderer(lineIdx: enLineIdx, content: model.string, cnt: len, t: m.title ?? "", beBlack: true))} else{// The red box on the right equals a line in Frame 1Copy the code

Previous blogs:

Core Text practice + : wrap, with word-level controls

Core Text practice: Customize the position of each word

github repo