gtag('config', 'G-0PFHD683JR');
Price Prediction

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 UIViewIncluding 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 MessageCellEnsuring 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.

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button