Abstract

It is very efficient for a short time, but it is not easy to use it without digestion.

This exploration idea is to query the official document, set different values to test the change of parameters in a single method, then test the execution sequence of the two methods, processing ideas, and finally think about the summary.

When summing up the processing logic of the method, pseudo-code is used to comb out the execution thought of the method. Avoid interpreting too much text and increasing the cost of understanding.

Recently, I learned small program development and got to know flex layout. I really like this fast and convenient way. So when you have a need to center text on a page, you want to handle it directly on UIlabel, and then set its inner margins on UIlabel (similar to flex layout). Instead of putting a View first. Then place a UILabel control on the view by setting the distance between the UILabel control and the parent view.

First look at the code implementation, the following code, is the solution after the search, if just take to use, directly copy into the project. You need to set textInsets before setting text

class SHLabel: UILabel {
​
   var textInsets: UIEdgeInsets = .zero
​
   override func drawText(in rect: CGRect) {
       super.drawText(in: rect.inset(by: textInsets))
   }
   
   override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
       
       let insets = textInsets
       var rect = super.textRect(forBounds: bounds.inset(by: insets), limitedToNumberOfLines: numberOfLines)
       
       rect.origin.x -= insets.left
       rect.origin.y -= insets.top
       rect.size.width += (insets.left + insets.right)
       rect.size.height += (insets.top + insets.bottom)
       return rect
   }
}
Copy the code

Why is the inside margin implemented this way?

The next step is to sort out why this is happening. First look at the developer documentation and see what these two methods do in the code block

function drawText(in rect: CGRect) textRect(forBounds bounds:, limitedToNumberOfLines numberOfLines) -> CGRect
The title inrectDraws text or shadows in the area Returns the recT area of the drawn text
detailed If you need to modify the drawing behavior in the label, you need to override this method. This method has been configured with a default environment and text color for drawing. In the overridden method, you can customize the drawing method and then call super or draw yourself. Override this method before the system performs other text computations (this is too hard to understand) if calledsizeToFit()andsizeThatFits(_:)This method is triggered
link Developer.apple.com/documentati… Developer.apple.com/documentati…

When verifying the execution order of the two methods and their respective functions, we found that when text assignment of UILabel is performed, the textRect method is called first, and then the drawText method is called.

TextRect is called again when the actual width of the textRect is greater than the actual width of the UILabel set. DrawText is also called after textRect has been called twice.

textRectThe role of

At this point, it seems understandable that the developer documentation mentions overriding this method before the system performs any other textual calculations. This method returns the bounds and text lines of UILabel, and returns the rect area of text to the system by calling super.

The execution logic (pseudocode) was found after several tests:

// Frame is set to frame if UIlabel. NumberOfLines == 1 {textRect is called. Return widht = text else {if text Return width = text widht} else {text Rect is called twice with the wdith of the frame Return size of rect = (width of frame, height of text)}}Copy the code

drawTextThe role of

Look at the rect argument in drawText, which is the recT returned by the textRect method. The actual drawing area of text is done by overriding the drawText method and calling its super method in it.

After many validations, the recT here is not exactly the recT returned in the textRect method. The relationship between them is (pseudocode) :

TextRect returns dx = frame.x dy = frame.y if frame.width is fixed {dwidth = Frame. Width} else {dwidth = rect.width} if frame. Height is fixed {dheight = frame. Height} else {dheight = rect.width} Return drawText rect(dx,dy,dwidth,dheight)Copy the code

Ask again: why is the inside margin implemented this way?

After reading these two methods patiently, I have some ideas about the problem in the problem. So let’s get this straight.

First of all, a common understanding is to set the inner margin of the UILabel. It is to determine the frame area of the UILabel and adjust the display area of the text. With this understanding, the next step is easy.

  • The first step is to usetextRectThe Bounds () method gets the display area of text, which by default is the same as the Bounds area of UILabel
  • Then we need to calculate the rect area of the new text text with the inner margin value we set ourselves.
  • End up withdrawTextThe Rect () method redraws the rect area display of text.

So why implement it this way? Because these are currently the only two methods that are directly related to text.

To optimize the

With so much theory, it’s time to export some dry goods.

If the frame of UILabel is determined, the important thing is that width and height are determined. So the textRect method doesn’t have to be rewritten.

class SHLabel: UILabel {
​
   var textInsets: UIEdgeInsets = .zero
   override func drawText(in rect: CGRect) {
       super.drawText(in: rect.inset(by: textInsets))
   }
}
Copy the code

Here you can see that when the height of the UILabel is uncertain, rewrite textRect to help determine the height of the UILabel. After verification, the x and y in this method are also not handled, so when is it used? I haven’t encountered one yet.

class SHLabel: UILabel {
​
   var textInsets: UIEdgeInsets = .zero
​
   override func drawText(in rect: CGRect) {
       super.drawText(in: rect.inset(by: textInsets))
   }
   
   override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
       let insets = textInsets
       var rect = super.textRect(forBounds: bounds.inset(by: insets), limitedToNumberOfLines: numberOfLines)
​
//       rect.origin.x -= insets.left
//       rect.origin.y -= insets.top
       rect.size.width += (insets.left + insets.right)
       rect.size.height += (insets.top + insets.bottom)
       return rect
   }
}
Copy the code

extension

That’s the end of the article. If you’re a detail junkie, feel like textRect is called both when it’s needed and when it’s not. The drawText method is always called whether or not the inner margin is set. Does this affect performance?

There are two solutions:

  1. Operational is to consider the need as much as possible and use it when you have to
  2. Psychological comfort, that is to let go. If you think about it, both of these methods are rewriting methods, what is the nature of rewriting? That is, do not execute your own method, do the override method. In other words, if the system doesn’t rewrite it, it will do it itself. The impact of this code on performance is trivial.

The newly discovered

All of a sudden, did you notice, we seem to understand, why UILabel doesn’t have to fix its height, it can determine its own height, it can completely display the text? You think. You think about it…