18

I want to give the following aspect to an UISegmentedControl:

enter image description here

Note the gray background view, and the white background of the segmented control non selected item.

But, if I give a white background to my UISegmentedControl, I get the following:

enter image description here

Note the white square corners around the UISegmentedControl. What should I do to avoid that square corners?

Thank you in advance,

EDIT: If I change the corner radius of the UISegmentedControl's layer, as suggested by onegray, the result is better, but not perfect (note the white line at the right):

enter image description here

neutrino
  • 2,297
  • 4
  • 20
  • 28

4 Answers4

16

Setting the _segmentedControl.layer.cornerRadius = 5; might help.

Update: More complex clip rect to get rid of 1px right space:

    CAShapeLayer* mask = [[CAShapeLayer alloc] init];
    mask.frame = CGRectMake(0, 0, _segmentedControl.bounds.size.width-1, _segmentedControl.bounds.size.height);
    mask.path = [[UIBezierPath bezierPathWithRoundedRect:mask.frame cornerRadius:4] CGPath];
    _segmentedControl.layer.mask = mask;

Update: Matthias Bauch provided a good explanation why this whitespace appears on the right side of the UISegmentedControl. So the simplest way to remove it is making segments of fixed size and adjusting them for proper width.

onegray
  • 5,105
  • 1
  • 23
  • 29
  • Thank you for your answer. Doing that, the result is better, but far from perfect. See image attached in my original post. – neutrino Oct 02 '13 at 14:46
  • Thank you again. Finally, I've gone with Matthias' answer, but you've helped me a lot. I've tested your solution, and it works for me too. – neutrino Oct 03 '13 at 08:57
10

If that should work for all UISegmentedControls it's a bit of a hassle. The problem is in iOS7 the 1 pt. border between two segments does not count to the size of the segment. E.g. if the frame of your UISegmentedControl is 320 pt. wide you have to remove 1 pt. and than divide by 2. And (320-1)/2 is 159.5. iOS floors this value down to 159 pt. And you end up with a 1 pt. border and two 159 pt. segments. Which is 319, and not 320. Hence the 1pt. line at the right of your segmentedControl.

There is a way to calculate the "actual" (the size of the rendering on screen) size of the segmentedControl. With that width you can then add a UIView with rounded corners below the UISegmentedControl. This code should work for all configurations, even if you have manually sized segments in your segmentedControl:

- (UIView *)addBackgroundViewBelowSegmentedControl:(UISegmentedControl *)segmentedControl {
    CGFloat autosizedWidth = CGRectGetWidth(segmentedControl.bounds);
    autosizedWidth -= (segmentedControl.numberOfSegments - 1); // ignore the 1pt. borders between segments

    NSInteger numberOfAutosizedSegmentes = 0;
    NSMutableArray *segmentWidths = [NSMutableArray arrayWithCapacity:segmentedControl.numberOfSegments];
    for (NSInteger i = 0; i < segmentedControl.numberOfSegments; i++) {
        CGFloat width = [segmentedControl widthForSegmentAtIndex:i];
        if (width == 0.0f) {
            // auto sized
            numberOfAutosizedSegmentes++;
            [segmentWidths addObject:[NSNull null]];
        }
        else {
            // manually sized
            autosizedWidth -= width;
            [segmentWidths addObject:@(width)];
        }
    }

    CGFloat autoWidth = floorf(autosizedWidth/(float)numberOfAutosizedSegmentes);
    CGFloat realWidth = (segmentedControl.numberOfSegments-1);      // add all the 1pt. borders between the segments
    for (NSInteger i = 0; i < [segmentWidths count]; i++) {
        id width = segmentWidths[i];
        if (width == [NSNull null]) {
            realWidth += autoWidth;
        }
        else {
            realWidth += [width floatValue];
        }
    }

    CGRect whiteViewFrame = segmentedControl.frame;
    whiteViewFrame.size.width = realWidth;

    UIView *whiteView = [[UIView alloc] initWithFrame:whiteViewFrame];
    whiteView.backgroundColor = [UIColor whiteColor];
    whiteView.layer.cornerRadius = 5.0f;
    [self.view insertSubview:whiteView belowSubview:segmentedControl];
    return whiteView;
}

Please take care of frame changes yourself.

See this screenshot to see the difference between the two controls. All frames are 280 pt. wide.

Because of the formula UISegmentedControl uses the first controls actual size is 278 pt. And the real size of the second one is 279 pt.

enter image description here


The problem is that this somehow relies on the implementation of UISegmentedControl. Apple could for example change the implementation so segmentWidth that end in .5 points will be displayed. They could easily do this on retina displays.

If you use this code you should check your app on new iOS versions as early as possible. We are relying on implementation details, and those could change every day. Fortunately nothing bad happens if they change the implementation. It will just not look good.

Matthias Bauch
  • 89,811
  • 20
  • 225
  • 247
2

I know this is kind of a hack but you could just use a rounded UIView with white background placed just underneath - and aligned with - the segmented control, except for the width which should be equal to the original control's width minus 1.

Result: Segmented Control with White Background

neural5torm
  • 773
  • 1
  • 9
  • 21
  • If you use Autolayout, you can Pin the "background view" to match width/height etc of the SegmentControl so no need to adjust with the minus 1. And of course you can use runtime attributes to set the cornerRadius of the background view too – wuf810 Sep 10 '15 at 08:10
0

Just to clarify Mattias Bauch's excellent answer. You need to set the returned view as a subview to the view (which we call yourMainView) where you have your segmented control:

UIView *segmControlBackground = [self addBackgroundViewBelowSegmentedControl:yourSegmentedControl];
[yourMainView addSubview:segmControlBackground];



And you need to, of course, declare the new method in your header (.h) file:

- (UIView *)addBackgroundViewBelowSegmentedControl:(UISegmentedControl *)segmentedControl;
OscarWyck
  • 2,515
  • 5
  • 21
  • 26