In this article, I’m going to use Core Text to handle ellipses in irregular Text,
In a different way,
In this article, I’ll show you how to use Core Text to display exotic Text effects
What’s weird about it?
If you look at the next two pictures,
The descriptive information in the middle of the first sheet, no more than three lines of text, is presented
The description in the middle of the second sheet, more than three lines of text,
Just type an ellipsis and add a full-text expansion button
What’s strange is the irregular shape of the text
The full text expansion button blocks part of the text,
The third line of text displays width narrower than the first two lines
Implementation idea:
It’s a little convoluted,
Get the first two lines of text display width (uI.std.width -cgfloat (16 * 2)),
So let’s figure out a text frame, CTFrame
- If the text is no more than three lines,
Every line of the text frame is drawn
- More than three lines of text,
Take the first two lines of the text frame and draw it
Get line 3 of the text frame, get line range,
Find the corresponding text for the third line, new
Get the third line of text display width (uI.std. width -16 left margin -offsetrhs right margin),
Right margin let offsetRhs: CGFloat = 28 button width + 29 button right margin + 10 button left margin + 5 space for ellipsis
Create the second text frame frameInner
Get the first CTLine on the second frameInner and find its corresponding text, subSecond, again through line range
Add ellipsis to subSecond, create CTLine, and draw
Class FrameZeroLabel: UIView{var frameRef: CTFrame? Var contentInfo: String? Var showDot = false init() {super.init(frame: CGRect.zero) backgroundColor = UIColor.white } override func draw(_ rect: CGRect) { guard let ctx = UIGraphicsGetCurrentContext(), let f = frameRef, let content = contentInfo else{ return } let xHigh = bounds.size.height ctx.textMatrix = CGAffineTransform.identity TranslateBy (x: 0, y: xHigh) ctx.scaleBy(x: 1.0, y: -1.0) guard let lines = CTFrameGetLines(f) as? [CTLine] else{ return } let lineCount = lines.count guard lineCount > 0 else { return } let total = max(lineCount, 3) var originsArray = [CGPoint](repeating: CGPoint.zero, count: CTFrameGetLineOrigins(f, CFRangeMake(0, 0), &originsArray) var lastY: CGFloat = 0 var frameY:CGFloat = 0 for i in 0.. <total{ var lineAscent:CGFloat = 0 var lineDescent:CGFloat = 0 var lineLeading:CGFloat = 0 CTLineGetTypographicBounds(lines[i] , &lineAscent, &lineDescent, &lineLeading) var lineOrigin = originsArray[i] switch i { case 0: frameY = lineOrigin.y default: LastY -= 1 frameY = frameY - (lineAscent + lineDescent) Y = frameY} lineOrigin. Y += lastY // Adjust to the desired coordinates CTx. textPosition = lineOrigin // This is the general processing // Switch I {case 0, 1: // Draw first two lines CTLineDraw(lines[I], CTX) default: ShowDot {lineRange = CTLineGetStringRange(lines[I]) let range = NSMakeRange(lineRange.location == kCFNotFound ? NSNotFound : lineRange.location, Linerange.length) let sub = content[range.location..<(range.location + range.length)] let new = String(sub) /// let page = new.plainX let calculatedSize = page.height(bound: 1000) let offsetRhs: CGFloat = 28 + 29 + 10 + 5 let siZ = CGSize(width: UI.std.width - 16 - offsetRhs, height: CalculatedSize. Height * 3) / / second frame let framesetter = CTFramesetterCreateWithAttributedString let the path = (page) CGPath(rect: CGRect(origin: CGPoint.zero, size: siZ), transform: nil) let frameInner = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, nil) if let lns = CTFrameGetLines(frameInner) as? [CTLine], lns.count > 0{ let lineRangeSecond = CTLineGetStringRange(lns[0]) let rangeSecond = NSMakeRange(lineRangeSecond.location == kCFNotFound ? NSNotFound : lineRangeSecond.location, LineRangeSecond. Length) // Find the corresponding text range let subSecond = new[rangeSecond. Location rangeSecond.length)] let newSecond = String(subSecond) + "..." let lnSecond = CTLineCreateWithAttributedString(newSecond.plainX) CTLineDraw(lnSecond, CTX)}} else{// No ellipsis CTLineDraw(lines[I], CTX)}}}}}Copy the code
Supplementary details:
How do you calculate it? Do you have an ellipsis?
Retrieves the list of ctLines from the text frame CTFrame
if let intro = m.introduction{ let page = intro.plainX let calculatedSize = page.height(bound: 3000) let siZ = CGSize(width: UI.std.width - CGFloat( 16 * 2 ), height: CalculatedSize. Height * 3) / / to build the core text text let framesetter = CTFramesetterCreateWithAttributedString let the path = (page) CGPath(rect: CGRect(origin: CGPoint.zero, size: siZ), transform: Nil) // As mentioned earlier, FrameRef = CTFramesetterCreateFrame(Framesetter, CFRangeMake(0, 0), path, Nil) if let lines = CTFrameGetLines(frameRef) as? [CTLine], lines.count > 2{ TopX_bottomY += 20 top_thebottomy constraint?.constraint. Update (offset: Topx_bottomy.neg) let toHid = (lines.count <= 3) Midtxt.zero.showdot = (toHid == false) expandb.ishidden = toHid // Hide, show more text button //... } / /... }Copy the code
Perfect functions:
Expand button to expand and collapse text
- 1. Two views are required, one of which is an abridged version, such as the two images above
One is the complete version, easy to understand
Don’t map the
- 2. The complete rendering of the second view,
Need to get the actual height of the full text frame
Here it is, after rendering, you get it
(Because you can customize the spacing of text when rendering)
class FrameOneLabel: UIView { var frameRef: CTFrame? weak var delegate: DrawDoneProxy? init() { super.init(frame: CGRect.zero) isHidden = true backgroundColor = UIColor.white } override func draw(_ rect: CGRect){ guard let ctx = UIGraphicsGetCurrentContext(), let f = frameRef else{ return } let xHigh = bounds.size.height ctx.textMatrix = CGAffineTransform.identity TranslateBy (x: 0, y: xHigh) ctx.scaleBy(x: 1.0, y: -1.0) guard let lines = CTFrameGetLines(f) as? [CTLine] else{ return } let lineCount = lines.count guard lineCount > 0 else { return } var originsArray = [CGPoint](repeating: CGPoint.zero, count: CTFrameGetLineOrigins(f, CFRangeMake(0, 0), &originsArray) Var lastY: CGFloat = 0 var final: CGFloat = 0 var first: CGFloat? = nil var frameY:CGFloat = 0 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] switch i { case 0: frameY = lineOrigin.y default: LastY -= 1 frameY = frameY - (lineAscent + lineDescent) Y = frameY} lineOrigin. Y += lastY // Adjust to the desired coordinates ctx.textPosition = lineOrigin CTLineDraw(line, Y} let typoH = lineAscent + lineDescent // The last line of y coordinates final = lineOrigin.y - typoH } let one: CGFloat = first ?? 0 // Let h = one-final // Get the height delegate of the text frame? .done(height: h) } }Copy the code
Review key points:
FrameZeroLabel Importance of the container view
The layout of the container view depends on external changes
The views drawn by CTFrame, especially the second one, must rely on the frames calculated by themselves
CTFrame
Drawing a view, if it depends on external changes,
It’s easy to stretch and compress
FrameZeroLabel
Container view,clipsToBounds
Then you can show it as required
Scene to strengthen
For the above function, add the following effect
-
Height before and after record
-
Initialization effect
Using the previous step, record the height
See GitHub Repo for details
And the original blog