[SOLVED] How to manage text size in segment control for different devices?

Issue

I am trying to create custom segment control using CollectionViewCell. So i can manage text size of the segment control in different devices. But it is not working properly in all the devices.
I have taken one cell inside collection view and used sizeForItemAt delegate method to equally set the cells. There will be only 3 cells. That is shown in the image.

This is the code for sizeForItemAt

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: (collectionView.frame.width / 3), height: 50.0)
}

Here is the output that i am getting
This is output in iPhone 8. Title is not properly visible

This is output in iPhone 12 Pro Max

so i am trying to find solution using which i can manage text size in all the devices and all cells can take equal spacing as well.

Solution

Below is one way I can think of doing it but by no means the only way.

First, I started by setting up a collection view with a horizontal flow layout.

// Flow layout configuration, mainly to figure out width of the cells
private func createLayout() -> UICollectionViewFlowLayout {
    
    let flowLayout = UICollectionViewFlowLayout()
    flowLayout.minimumLineSpacing = horizontalPadding
    flowLayout.minimumInteritemSpacing = 0
    flowLayout.scrollDirection = .horizontal
    
    // Calculate the available width to divide the segments evenly
    var availableWidth = UIScreen.main.bounds.width
    
    // There will always be segments - 1 gaps, for 3 segments, there will be
    // 2 gaps and for 4 segments there will be 3 gaps etc
    availableWidth -= horizontalPadding * CGFloat(segments.count - 1)
    
    let cellWidth = availableWidth / CGFloat(segments.count)
    
    flowLayout.itemSize = CGSize(width: cellWidth,
                                 height: collectionViewHeight)
    
    return flowLayout
}

Once I do this, I run into the same problem, that depending on the width of the screen, my text could get cut off.

UICollectionView UISegmentController UICollectionViewCell custom swift iOS

So once the width of each segment is determined, we have to calculate the maximum font size to show the complete text for the longest segment and that font size should be applied to all

In this case, the long segment is Vibration Intensity in terms of string length.

// Flow layout configuration, mainly to figure out width of the cells
private func createLayout() -> UICollectionViewFlowLayout {
    
    let flowLayout = UICollectionViewFlowLayout()
    flowLayout.minimumLineSpacing = horizontalPadding
    flowLayout.minimumInteritemSpacing = 0
    flowLayout.scrollDirection = .horizontal
    flowLayout.sectionInset = UIEdgeInsets(top: 0,
                                           left: horizontalPadding,
                                           bottom: 0,
                                           right: horizontalPadding)
    
    // Calculate the available width to divide the segments evenly
    var availableWidth = UIScreen.main.bounds.width
    
    // There will always be segments - 1 gaps, for 3 segments, there will be
    // 2 gaps and for 4 segments there will be 3 gaps etc
    availableWidth -= horizontalPadding * CGFloat(segments.count - 1)
    
    // Remove the insets
    availableWidth -= flowLayout.sectionInset.left + flowLayout.sectionInset.right
    
    let cellWidth = availableWidth / CGFloat(segments.count)
    
    // Add this function
    calculateApproxFontSize(forWidth: cellWidth)
    
    flowLayout.itemSize = CGSize(width: cellWidth,
                                 height: collectionViewHeight)
    
    return flowLayout
}

private func calculateApproxFontSize(forWidth width: CGFloat) {
    
    // Get the longest segment by length
    if let longestSegmentTitle = segments.max(by: { $1.count > $0.count }) {
        
        let tempLabel = UILabel()
        tempLabel.numberOfLines = 1
        tempLabel.text = longestSegmentTitle
        tempLabel.sizeToFit()
        
        guard var currentFont = tempLabel.font else { return }
        
        var intrinsicSize
            = (longestSegmentTitle as NSString).size(withAttributes: [.font : currentFont])
        
        // Keep looping and reduce the font size till the text
        // fits into the label
        // This could be optimized further using binary search
        // However this should be ok for small strings
        while intrinsicSize.width > width
        {
            currentFont = currentFont.withSize(currentFont.pointSize - 1)
            tempLabel.font = currentFont
            
            intrinsicSize
                = (longestSegmentTitle as NSString).size(withAttributes: [.font : currentFont])
        }

        // Set the font of the current label
        // segmentFontSize is a global var in the VC
        segmentFontSize = currentFont.pointSize
    }
}

Then set the segmentFontSize in cellForItemAt indexPath

func collectionView(_ collectionView: UICollectionView,
                    cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    
    let cell = collectionView
        .dequeueReusableCell(withReuseIdentifier: SegmentCell.reuseIdentifier,
                             for: indexPath) as! SegmentCell
    
    cell.backgroundColor = .orange
    cell.title.text = segments[indexPath.item]
    
    // Adjust the font
    cell.title.font = cell.title.font.withSize(segmentFontSize)
    
    return cell
}

This will give you something like this:

Resize UILabel to fit frame rect in swift iOS using UICollectionView as UISegmentView

If you found some part difficult to follow, here is the link to the complete code: https://gist.github.com/shawn-frank/03bc06d13f90a54e23e9ea8c6f30a70e

Answered By – Shawn Frank

Answer Checked By – Mary Flores (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published.