How can I calculate correct widths for UISegmentedControl segments?

14,042

Solution 1

If you can support iOS 5 and later then you can use the property apportionsSegmentWidthsByContent and set it to YES.

From the iOS 5 docs:

apportionsSegmentWidthsByContent

Indicates whether the control attempts to adjust segment widths based on their content widths.

@property(nonatomic) BOOL apportionsSegmentWidthsByContent

Discussion

If the value of this property is YES, for segments whose width value is 0, the control attempts to adjust segment widths based on their content widths.

Solution 2

EDIT: an easier option might be to use the iOS 5 property apportionsSegmentWidthsByContent as mentioned in @Camsoft's answer here


Well, I ended up getting the UIFont from poking thru the subviews (which could break in a future iOS). Being a modular/reuse aficianado, I wrote this routine to do it and then to distribute the space so that the segments in the control are evenly spaced for the titles.

-(void)resizeSegmentsToFitTitles:(UISegmentedControl*)segCtrl {
    CGFloat totalWidths = 0;    // total of all label text widths
    NSUInteger nSegments = segCtrl.subviews.count;    
    UIView* aSegment = [segCtrl.subviews objectAtIndex:0];
    UIFont* theFont = nil;

    for (UILabel* aLabel in aSegment.subviews) {
        if ([aLabel isKindOfClass:[UILabel class]]) {
            theFont = aLabel.font;
            break;
        }
    }

    // calculate width that all the title text takes up
    for (NSUInteger i=0; i < nSegments; i++) {
        CGFloat textWidth = [[segCtrl titleForSegmentAtIndex:i] sizeWithFont:theFont].width;
        totalWidths += textWidth;
    }

    // width not used up by text, its the space between labels
    CGFloat spaceWidth = segCtrl.bounds.size.width - totalWidths;   

    // now resize the segments to accomodate text size plus 
    // give them each an equal part of the leftover space
    for (NSUInteger i=0; i < nSegments; i++) {
        // size for label width plus an equal share of the space
        CGFloat textWidth = [[segCtrl titleForSegmentAtIndex:i] sizeWithFont:theFont].width;
        // roundf??  the control leaves 1 pixel gap between segments if width 
        // is not an integer value, the roundf fixes this
        CGFloat segWidth = roundf(textWidth + (spaceWidth / nSegments));    
        [segCtrl setWidth:segWidth forSegmentAtIndex:i];
    }
}

Solution 3

NSArray *itemArray = [NSArray arrayWithObjects: @"Title1", @"Title2", @"Titl3", @"Title4",nil];
segmentedControl = [[UISegmentedControl alloc] initWithItems:itemArray];
segmentedControl.frame = CGRectMake(0, 0, 310, 35);
segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
segmentedControl.selectedSegmentIndex = 0;
[segmentedControl addTarget:self action:@selector(pickOne:) forControlEvents:UIControlEventValueChanged];   
segmentedControl.tintColor=[UIColor grayColor];

for (id segment in [segmentedControl subviews]) 
{
    for (id label in [segment subviews]) 
    {
        if ([label isKindOfClass:[UILabel class]])
        {

            [label setTextAlignment:UITextAlignmentCenter];
            [label setFont:[UIFont boldSystemFontOfSize:12]];
        }
    }           
}
Share:
14,042
progrmr
Author by

progrmr

Software engineer developing iOS apps at Dexcom. I've previously worked on map apps at ESRI, embedded avionics software for JSF, network planning software to optimize use of satellite constellations, compiler/debugger development at TeleSoft, operating systems at SofTech Microsystems, and other projects. I specialize in communications and GPS navigation systems as a result of my experience with amateur radio (N6DM) and aviation. I started out as a radio/computer technician, then programming in 6502 assembly, designing hardware and software for the single board Apple II many years ago. Then worked on operating systems at SofTech Microsystems, Ada compilers at Telesoft (aka Alsys, Aonix, Atego), before moving on to embedded software development and mobile applications.

Updated on June 24, 2022

Comments

  • progrmr
    progrmr about 2 years

    I'm trying to use a UISegmentedControl but having trouble calculating the width of the segments. The control makes the segments all the same width which doesn't work for some titles, like this one:

    http://morrisphotoart.com/tmp/Screenshot2011-07-13_21.17.33.png

    I can write code to calculate the segment width if I knew which font and call the setWidth:forSegmentAtIndex: method but how can I get the font? Or is there another way?

    The left and middle segment's titles are not fixed so I can't hardcode the widths.

  • stack2012
    stack2012 almost 13 years
    You are Welcome ! :) Happy coding !
  • progrmr
    progrmr almost 13 years
    I didn't really want to have to poke down into the subviews, but there doesn't seem to be any other way. I did get it to work by getting the font from the label subview, then I could calculate the title widths and divide up the space evenly.
  • stack2012
    stack2012 almost 13 years
    Yep. That was the best possible solution I could find when I was into the same trouble.
  • Christian Gossain
    Christian Gossain over 12 years
    This is some solid code right here! I just added this method to my view controller, passed it my segmented control in my viewDidLoad method and it just took care of the rest! didn't even have to modify anything.
  • progrmr
    progrmr over 12 years
    Except it doesn't handle the case where there is NO space left, that is where your label texts are too wide for the total width of the control, but other than minor flaw it works ;-)
  • Krzysztof Przygoda
    Krzysztof Przygoda about 10 years
    Thx progrmr! Unfortunately, this does not work any longer with iOS 7. Corrected and simplified function published here.
  • Josh
    Josh almost 8 years
    Sweet. Works in ios 9.3, Swift.
  • Habib Ali
    Habib Ali over 7 years
    CGFloat segCtrlWidth = segCtrlRect.size.width + segCtrl.numberOfSegments*em; This is not a valid way of calculating width you are just multiplying number of segments with font size. Valid way should be calculating width on basis of text in each segment
  • Krzysztof Przygoda
    Krzysztof Przygoda about 7 years
    @HabibAli - You're right - that's why there is an em unit, if you know what I mean. So, if this is somehow satisfactory to you, please rethink your downvote.
  • Habib Ali
    Habib Ali about 7 years
    em = font.pointSize is not satisfactory as font size only defines height not the width of text of each segment. Got it ?
  • Krzysztof Przygoda
    Krzysztof Przygoda about 7 years
    Note that all your “magic” happens in [segCtrl sizeToFit] (based on apportionsSegmentWidthsByContent) and em is used here only as a padding (whitespace).