1

I am create editable label with dynamic size. I am using pan gesture to scale my view and I want increase font size as per view. I used that code

func scaleGesture(recognizer: UIPanGestureRecognizer)
{
    DispatchQueue.main.async { [self] in
        
        let touchPoint = recognizer.location(in: view)
        
        if recognizer.state == .began
        {
            initialBounds = vv.bounds
            initialDistance = CGpointGetDistance(vv.center, point2: touchPoint)
            vv.translatesAutoresizingMaskIntoConstraints = true
        }
        else if recognizer.state == .changed
        {
            let horizontalChange = recognizer.translation(in: view).x
            let scale = sqrtf(Float(CGpointGetDistance(vv.center, point2: touchPoint)) / Float(initialDistance!))
            vv.frame = CGRect(x:  vv.frame.origin.x , y: vv.frame.origin.y, width: initialBounds!.width + horizontalChange, height: initialBounds!.height + horizontalChange)
            vv.contentLabel.fitFontForSize(vv.frame.size)
        }
        else
        {
            fontSize = vv.contentLabel.font.pointSize
        }

    }
}

As per code I got this output. Please check this clip In this clip you can see that label font size is not working smooth. Please help me

Kamlesh Shingarakhiya
  • 2,757
  • 2
  • 16
  • 34

1 Answers1

1

I cannot say for sure because it is hard to understand fully what some of the variables like vv etc.

However, I believe the main issues for getting choppy results could be:

  1. There is something wrong with how you are calculating the font for the current frame
  2. It seems you are only adjusting the font when the gesture has ended whereas I think it would be useful even when it is being changed

One method would be to follow these steps:

  1. Get the new bounds (mainly width and height) of your view
  2. Give your label a maximum font size
  3. Keep reducing the font till you get a font that fits the new size of the view

This will be easy and should work if the text is not too large

After doing some research myself as this was an interesting problem, I came across some elegant solutions to find the optimal font size using binary search

So here is how I implemented it:

1. Custom UILabel subclass called FlexiFontLabel

The logic is explained in the comments

class FlexiFontLabel: UILabel
{
    // Boundary of minimum and maximum
    private let maxFontSize = CGFloat(500)
    private let minFontSize = CGFloat(5)
    
    // Margin of error is needed in binary search
    // so we can return when reach close enough
    private let marginOFError = CGFloat(0.5)
    
    // layoutSubviews() will get called while updating
    // font size so we want to lock adjustments if
    // processing is already in progress
    private var isUpdatingFontSize = false
    
    // Once this is set to true, the label should only
    // only support multiple lines rather than one
    var doesAdjustFontSizeToFitFrame = false
    {
        didSet
        {
            if doesAdjustFontSizeToFitFrame
            {
                numberOfLines = 0
            }
        }
    }
    
    // Adjusting the frame of the label automatically calls this
    override func layoutSubviews()
    {
        super.layoutSubviews()
        
        // Make sure the label is set to auto adjust the font
        // and it is not currently processing the font size
        if doesAdjustFontSizeToFitFrame
            && !isUpdatingFontSize
        {
            adjustFontSizeIfRequired()
        }
    }
    
    
    /// Adjusts the font size to fit the label's frame using binary search
    private func adjustFontSizeIfRequired()
    {
        guard let currentText = text,
              var currentFont = font else
        {
            print("failed")
            return
        }
        
        // Lock function from being called from layout subviews
        isUpdatingFontSize = true
        
        // Set max and min font sizes
        var currentMaxFontSize = maxFontSize
        var currentMinFontSize = minFontSize
        
        while true
        {
            // Binary search between min and max
            let midFontSize = (currentMaxFontSize + currentMinFontSize) / 2;
            
            // Exit if approached minFontSize enough
            if (midFontSize - currentMinFontSize <= marginOFError)
            {
                // Set min font size and exit because we reached
                // the biggest font size that fits
                currentFont = UIFont(name: currentFont.fontName,
                                     size: currentMinFontSize)!
                
                break;
            }
            else
            {
                // Set the current font size to the midpoint
                currentFont = UIFont(name: currentFont.fontName,
                                     size: midFontSize)!
            }
            
            // Configure an attributed string which can be used to find an
            // appropriate rectangle for a font size using its boundingRect
            // function
            let attribute = [NSAttributedString.Key.font: currentFont]
            let attributedString = NSAttributedString(string: currentText,
                                                      attributes: attribute)
            
            let options: NSStringDrawingOptions = [.usesLineFragmentOrigin,
                                                   .usesFontLeading]
            
            // Get a bounding box with the width of the current label and
            // an unlimited height
            let constrainedSize = CGSize(width: frame.width,
                                         height: CGFloat.greatestFiniteMagnitude)
            
            // Get the appropriate rectangle for the text using the current
            // midpoint font
            let newRect = attributedString.boundingRect(with: constrainedSize,
                                                        options: options,
                                                        context: nil)
            
            // Get the current area of the new rect and the current
            // label's bounds
            let newArea = newRect.width * newRect.height
            let currentArea = bounds.width * bounds.height
            
            // See if the new frame is lesser than the current label's area
            if newArea < currentArea
            {
                // The best font size is in the bigger half
                currentMinFontSize = midFontSize + 1
            }
            else
            {
                // The best font size is in the smaller half
                currentMaxFontSize = midFontSize - 1
            }
        }
        
        // set the font of the current label
        font = currentFont
        
        // Open label to be adjusted again
        isUpdatingFontSize = false
    }
}

2. How to use the label

It is like setting up any UILabel with one small addition

let label = FlexiFontLabel()
/// Do all your set up like frames, colors
// alignments etc

// This opens the label to auto adjust itself
label.doesAdjustFontSizeToFitFrame = true

3. Pan Gesture Implementation Nothing interesting happens here as all the adjustment happens in the UILabel subclass, however I just want to show you what I did for better understanding

@objc
func scaleGesture(recognizer: UIPanGestureRecognizer)
{
    let location = recognizer.location(in: view)
    
    // This is my logic to prevent the bottom right anchor going
    // below a certain threshold, you can ignore it as it has
    // nothing to do with your solution
    if location.x >= minX && location.y >= minY
    {
        // Update the position of the anchor
        circle.center = location
        
        // Calculate the new frame for the label
        var newLabelFrame = label.frame
        let newWidth = location.x - originalLabelFrame.origin.x
        let newHeight = location.y - originalLabelFrame.origin.y
        newLabelFrame.size = CGSize(width: newWidth,
                                    height: newHeight)
        
        // After updating the label, layoutSubviews() will be called
        // automatically which will update the font size
        label.frame = newLabelFrame
    }
}

This gives me the following result which is quite smooth when changing the font size according to the label frame

adjust font size of label to fit the rectangle CGRect frame bounds UILabel auto adjust resize font size calculate max font that fit rectangle calculate font to fit frame

Shawn Frank
  • 4,381
  • 2
  • 19
  • 29