Ditch Autolayout, speed up your application: the old school trick that works
This is the second part of the articles course on improving IOS application performancePlease read the first part here.
Why Autolayout may slow down in your application and how to fix it
Autolayout It is the standard way to place and size user interface elements in iOS applications. It is flexible, strong and wonderful to deal with different screen sizes. But this is hunting: it’s not always the most efficient. When your interface contains dynamically adjusting items (such as nomenclature change their size based on content), Autolayout can provide noticeable performance problems, especially on old devices.
Initially, the slowdown may be accurate. Some elements? It is not a big deal. But with the complexity of your user interface, the performance takes great success. Passing. Details of the animation. The smoothness you expect? gold.
So, what is the alternative? Manual frame planning. Instead of relying on Autolayout, you can explicitly calculate and set tires. Yes, this means writing more software instructions, but barter is speed. The user interface feels inflammation, and performance improves significantly.
Let’s take a common example: the weather forecast cell. Instead of accumulating autolaout restrictions, you can hand manually using frame
Values. This removes the general expenses of the automatic registration solution in Autolayout, which makes user interface updates much faster.
But keep in mind: Handicrafts are rigid. If the size of the cell or line changes, these fixed values will not be automatically adjusted. You need to deal with updates yourself.
What about the automatic mask in the old school in the facility of the façade? Forget that. It was useful before Autolayout, but now it turns into restrictions behind the scenes.
Before planning, disable the creation of automatic restrictions for all items.
Let’s say that we want to fix the size a Messagecell:
@IBOutlet weak var messageLabel: UILabel! {
didSet {
messageLabel.translatesAutoresizingMaskIntoConstraints = false
}
}
@IBOutlet weak var timestampLabel: UILabel! {
didSet {
timestampLabel.translatesAutoresizingMaskIntoConstraints = false
}
}
@IBOutlet weak var avatarImageView: UIImageView! {
didSet {
avatarImageView.translatesAutoresizingMaskIntoConstraints = false
}
}
After that, we go beyond layoutSubviews
The method, where all elements of children UIView
Including stickers and pictures – they are placed. The essence of manual design is to calculate the size of each element and coordinates independently, then customize it using frame
ownership.
Let’s add a feature to determine the filling. It can be the same for all sides (for example, 10 points) or different to precisely control the spacing. This will help ensure proper alignment The text of the messageand ChronologicalAnd Avatar picture inside MessageCell
.
let insets: CGFloat = 10.0
Calculate the size of the text in Uilabel:
func getLabelSize(text: String, font: UIFont) -> CGSize {
// Define the maximum width of the text - this is the width of the cell minus left and right padding
let maxWidth = bounds.width - insets * 2
// Get the dimensions of the block for the label
let textBlock = CGSize(width: maxWidth, height: CGFloat.greatestFiniteMagnitude)
// Get a rectangle for the text in this block and specify the font
let rect = text.boundingRect(
with: textBlock,
options: .usesLineFragmentOrigin,
attributes: [.font: font],
context: nil
)
// Get the width and height of the block, rounding up to the nearest integer
let width = ceil(rect.size.width)
let height = ceil(rect.size.height)
return CGSize(width: width, height: height)
}
In this method, we pass the rectangle dimensions where the text will be presented. the The offer is restrictedBut the height remains unlimited, which allows the text to be expanded as needed.
Then we use boundingRect
A way to determine the actual dimensions of the text within this mass. If the text exceeds the permitted view, it will automatically wrap the next line. The line also plays an important role in determining the final size.
To ensure accurate serving, we tour the offer and half to the nearest full number. This helps IOS efficiently convert dimensions into pixels.
As a result, we get the exact size of the rectangle needed to fit the text when displayed in the specified line. By adjusting the Uilabel frame on this size, we guarantee that the entire message is visible without an unnecessary additional or unnecessary space.
The next step is to place UILabel
inside MessageCell
Ensuring the appropriate alignment along with the avatar and chronological image image.
func messageLabelFrame() {
// Get the text size by passing the text and font
guard let text = messageLabel.text, !text.isEmpty else { return }
let messageLabelSize = getLabelSize(text: text, font: messageLabel.font)
// Calculate the X coordinate (centered horizontally)
let messageLabelX = (bounds.width - messageLabelSize.width) / 2
// Get the top-left corner point of the label
let messageLabelOrigin = CGPoint(x: messageLabelX, y: insets)
// Set the frame for UILabel
messageLabel.frame = CGRect(origin: messageLabelOrigin, size: messageLabelSize)
}
We calculate the size of the message naming, focus it horizontally, and put it using a CGRect
Based on the removal of the highest left. Determining this framework guarantees the correct placement.
The labels follow the same logic, but its own Y for the right alignment is modified, usually in the right upper or lower upper corner of the cell.
func timestampLabelFrame() {
// Ensure text exists to avoid force unwrapping
guard let text = timestampLabel.text, !text.isEmpty else { return }
// Get the text size
let timestampLabelSize = getLabelSize(text: text, font: timestampLabel.font)
// Calculate the X coordinate (align to the right with padding)
let timestampLabelX = bounds.width - timestampLabelSize.width - insets
// Calculate the Y coordinate (align to the bottom with padding)
let timestampLabelY = bounds.height - timestampLabelSize.height - insets
// Set the frame for UILabel
timestampLabel.frame = CGRect(origin: CGPoint(x: timestampLabelX, y: timestampLabelY), size: timestampLabelSize)
}
The last step is to put a picture of the avatar. Because its size is fixed (for example, 50 pointsWe simply put it without the need to calculate its dynamic dimensions. Usually, the avatar is aligned with the left side of the cell with some filling.
func avatarImageFrame() {
let avatarSize: CGFloat = 50
let avatarSizeDimensions = CGSize(width: avatarSize, height: avatarSize)
// Align avatar to the left with padding
let avatarOrigin = CGPoint(x: insets, y: bounds.midY - avatarSize / 2)
avatarImageView.frame = CGRect(origin: avatarOrigin, size: avatarSizeDimensions)
}
Let’s redefine the method of calculating the position of the elements.
override func layoutSubviews() {
super.layoutSubviews()
messageLabelFrame()
timestampLabelFrame()
avatarImageFrame()
}
To ensure that The text of the message and the update of the time dynamicallyWe need to add Setter methods that update the text and lead to a layout re -calculating. Here is how to do that:
func setMessage(text: String) {
messageLabel.text = text
messageLabelFrame() // Recalculate position
}
func setTimestamp(text: String) {
timestampLabel.text = text
timestampLabelFrame() // Recalculate position
}
In these methods, we set the text submitted on the opposite poster and then re -calculate its frame to ensure proper location. It seems that the full list of category like this:
import UIKit
class MessageCell: UICollectionViewCell {
@IBOutlet weak var messageLabel: UILabel! {
didSet {
messageLabel.translatesAutoresizingMaskIntoConstraints = false
}
}
@IBOutlet weak var timestampLabel: UILabel! {
didSet {
timestampLabel.translatesAutoresizingMaskIntoConstraints = false
}
}
@IBOutlet weak var avatarImageView: UIImageView! {
didSet {
avatarImageView.translatesAutoresizingMaskIntoConstraints = false
}
}
let insets: CGFloat = 10.0
let message
func setMessage(text: String) {
messageLabel.text = text
messageLabelFrame()
}
func setTimestamp(text: String) {
timestampLabel.text = text
timestampLabelFrame()
}
override func layoutSubviews() {
super.layoutSubviews()
messageLabelFrame()
timestampLabelFrame()
avatarImageFrame()
}
func getLabelSize(text: String, font: UIFont) -> CGSize {
let maxWidth = bounds.width - insets * 2
let textBlock = CGSize(width: maxWidth, height: CGFloat.greatestFiniteMagnitude)
let rect = text.boundingRect(with: textBlock, options: .usesLineFragmentOrigin,
attributes: [.font: font], context: nil)
return CGSize(width: ceil(rect.width), height: ceil(rect.height))
}
func messageLabelFrame() {
guard let text = messageLabel.text, !text.isEmpty else { return }
let messageLabelSize = getLabelSize(text: text, font: messageLabel.font)
let messageLabelX = avatarImageView.frame.maxX + insets
let messageLabelOrigin = CGPoint(x: messageLabelX, y: insets)
messageLabel.frame = CGRect(origin: messageLabelOrigin, size: messageLabelSize)
}
func timestampLabelFrame() {
guard let text = timestampLabel.text, !text.isEmpty else { return }
let timestampLabelSize = getLabelSize(text: text, font: timestampLabel.font)
let timestampLabelX = bounds.width - timestampLabelSize.width - insets
let timestampLabelY = bounds.height - timestampLabelSize.height - insets
let timestampLabelOrigin = CGPoint(x: timestampLabelX, y: timestampLabelY)
timestampLabel.frame = CGRect(origin: timestampLabelOrigin, size: timestampLabelSize)
}
func avatarImageFrame() {
let avatarSize: CGFloat = 50
let avatarX: CGFloat = insets
let avatarY: CGFloat = (bounds.height - avatarSize) / 2
avatarImageView.frame = CGRect(x: avatarX, y: avatarY, width: avatarSize, height: avatarSize)
}
}
You now need to set the text in the illustrations using the methods:
cell.setTimestamp(text: time)
cell.setMessage(text: String(someMessage))
conclusion
Stay away from Autolayout And adoption Manual frame planningWe remove the public expenditures caused by the dynamic registration accounts. As a result, the group display method works without autolaout, which greatly enhances its performance.
This improvement leads to the smoother scrolling, faster introduction, and a more responsive user interface, especially when dealing with complex or dynamic content. Although manual planning requires more software instructions and accurate dealing, barter deserves this to applications that concern every millimeter of things.