Customizing Left & Right UISegmentedControl Buttons

21,932

Solution 1

You need to provide the following images:

  • segment background selected (this has both left and right caps)
    enter image description here
  • segment background unselected (this has both left and right caps)
    enter image description here
  • segment middle, left selected, right unselected
    enter image description here
  • segment middle, left unselected, right selected
    enter image description here
  • segment middle, both left & right selected
    enter image description here
  • segment middle, both left & right unselected
    enter image description here

And then use the following code to set:

/* Unselected background */
UIImage *unselectedBackgroundImage = [[UIImage imageNamed:@"segment_background_unselected"] resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)];
[[UISegmentedControl appearance] setBackgroundImage:unselectedBackgroundImage
                                           forState:UIControlStateNormal
                                         barMetrics:UIBarMetricsDefault];

/* Selected background */
UIImage *selectedBackgroundImage = [[UIImage imageNamed:@"segment_background_selected"] resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)];
[[UISegmentedControl appearance] setBackgroundImage:selectedBackgroundImage
                                           forState:UIControlStateSelected
                                         barMetrics:UIBarMetricsDefault];

/* Image between two unselected segments */
UIImage *bothUnselectedImage = [[UIImage imageNamed:@"segment_middle_unselected"] resizableImageWithCapInsets:UIEdgeInsetsMake(15, 0, 15, 0)];
[[UISegmentedControl appearance] setDividerImage:bothUnselectedImage
                             forLeftSegmentState:UIControlStateNormal
                               rightSegmentState:UIControlStateNormal
                                      barMetrics:UIBarMetricsDefault];

/* Image between segment selected on the left and unselected on the right */
UIImage *leftSelectedImage = [[UIImage imageNamed:@"segment_middle_left_selected"] resizableImageWithCapInsets:UIEdgeInsetsMake(15, 0, 15, 0)];
[[UISegmentedControl appearance] setDividerImage:leftSelectedImage
                             forLeftSegmentState:UIControlStateSelected
                               rightSegmentState:UIControlStateNormal
                                      barMetrics:UIBarMetricsDefault];

/* Image between segment selected on the right and unselected on the left */
UIImage *rightSelectedImage = [[UIImage imageNamed:@"segment_middle_right_selected"] resizableImageWithCapInsets:UIEdgeInsetsMake(15, 0, 15, 0)];
[[UISegmentedControl appearance] setDividerImage:rightSelectedImage
                             forLeftSegmentState:UIControlStateNormal
                               rightSegmentState:UIControlStateSelected
                                      barMetrics:UIBarMetricsDefault];

Note that you'll have to adjust the cap size in the stretchable images to match your images.

Solution 2

Maurizio's answer didn't quite work for me with a segmented control within a toolbar; it kept showing these phantom lines on the controls since the divider images weren't wide enough.

So I made my own. Here are all of the images you will need for Xcode and I also put in the Photoshop files for creating the segmented control images so you can change the styling:

https://s3.amazonaws.com/iwasrobbed/stackoverflow/Custom+Segmented+Control.zip

Put this in your AppDelegate to have it change the appearance, using the attached images, of all segmented controls within a toolbar:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [self customizeAppAppearance];
}

- (void)customizeAppAppearance
{
    // Toolbar
    [[UIToolbar appearance] setBackgroundImage:[[UIImage imageNamed:@"toolbar.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(22, 5, 22, 5)] forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];

    // Segmented Controls within Toolbars

    // Unselected background
    UIImage *unselectedBackgroundImage = [[UIImage imageNamed:@"segmentInactive.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(15, 15, 15, 15)];
    [[UISegmentedControl appearanceWhenContainedIn:[UIToolbar class], nil] setBackgroundImage:unselectedBackgroundImage
                                                                                     forState:UIControlStateNormal
                                                                                   barMetrics:UIBarMetricsDefault];

    // Selected background
    UIImage *selectedBackgroundImage = [[UIImage imageNamed:@"segmentActive.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(15, 15, 15, 15)];
    [[UISegmentedControl appearanceWhenContainedIn:[UIToolbar class], nil] setBackgroundImage:selectedBackgroundImage
                                                                                     forState:UIControlStateSelected
                                                                                   barMetrics:UIBarMetricsDefault];

    // Image between two unselected segments
    UIImage *bothUnselectedImage = [[UIImage imageNamed:@"segmentBothInactive.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(15, 10, 15, 10)];
    [[UISegmentedControl appearanceWhenContainedIn:[UIToolbar class], nil] setDividerImage:bothUnselectedImage
                                                                       forLeftSegmentState:UIControlStateNormal
                                                                         rightSegmentState:UIControlStateNormal
                                                                                barMetrics:UIBarMetricsDefault];

    // Image between segment selected on the left and unselected on the right
    UIImage *leftSelectedImage = [[UIImage imageNamed:@"segmentLeftActiveRightInactive.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(15, 0, 15, 0)];
    [[UISegmentedControl appearanceWhenContainedIn:[UIToolbar class], nil] setDividerImage:leftSelectedImage
                                                                       forLeftSegmentState:UIControlStateSelected
                                                                         rightSegmentState:UIControlStateNormal
                                                                                barMetrics:UIBarMetricsDefault];

    // Image between segment selected on the right and unselected on the left
    UIImage *rightSelectedImage = [[UIImage imageNamed:@"segmentRightActiveLeftInactive.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(15, 0, 15, 0)];
    [[UISegmentedControl appearanceWhenContainedIn:[UIToolbar class], nil] setDividerImage:rightSelectedImage
                                                                       forLeftSegmentState:UIControlStateNormal
                                                                         rightSegmentState:UIControlStateSelected
                                                                                barMetrics:UIBarMetricsDefault];
}

Solution 3

You need to make one background image that is for all your segments, and also an image that is just the left edge of a button, an image that is the join between two buttons, and an image that is the right edge. Some of those need to be done for multiple states. So here is your image list:

  • left cap, selected
  • left cap, unselected
  • segment background, selected
  • segment background, unselected
  • right cap, selected
  • right cap, unselected
  • middle cap, left selected right unselected
  • middle cap, left unselected, right selected
  • middle cap, both selected
  • middle cap, both unselected

For the middle caps you can put them in like this: (text from Apple docs).

// Image between two unselected segments.
[mySegmentedControl setDividerImage:image1 forLeftSegmentState:UIControlStateNormal
              rightSegmentState:UIControlStateNormal barMetrics:barMetrics];
// Image between segment selected on the left and unselected on the right.
[mySegmentedControl setDividerImage:image1 forLeftSegmentState:UIControlStateSelected
              rightSegmentState:UIControlStateNormal barMetrics:barMetrics];
// Image between segment selected on the right and unselected on the right.
[mySegmentedControl setDividerImage:image1 forLeftSegmentState:UIControlStateNormal
              rightSegmentState:UIControlStateSelected barMetrics:barMetrics];

If you're using UIAppearance, obviously you'd send those messages to the appearance proxy.

Share:
21,932

Related videos on Youtube

Year3000
Author by

Year3000

Updated on May 30, 2020

Comments

  • Year3000
    Year3000 almost 4 years

    I'm trying to customize the following segmented control, using a left image for the first button and a right image for the second button. How would I do this using UIAppearance?

    I want to change the following segmentedControl:

    enter image description here

    to something similar like below:

    enter image description here

    The reason I want to use a custom image is so that I can change the corners of the buttons. If you look at the blue segmented control, it's more squared (my image has it's own corners).

    I was thinking of something like this but no use:

    UIImage *leftImage = [[UIImage imageNamed:@"leftControl.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
    UIImage *rightImage = [[UIImage imageNamed:@"rightControl.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
    
    [[UISegmentedControl appearance] setBackgroundImage:leftImage forState:UIControlStateNormal barMetrics:UIBarMetricsDefault ];
    [[UISegmentedControl appearance] setBackgroundImage:rightImage forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
    
  • Thom
    Thom over 11 years
    What's the exact mapping between the list of images above and the calls to set the control/appearance proxy's background/divider images?
  • Tom DeMille
    Tom DeMille about 11 years
    Yes, thank you, the images saved lots of time and research, much appreicated
  • avelis
    avelis about 11 years
    Considering the images described above, do you need to provide the HD equivalent (i.e. @2x.png) or does the resizableImageWithCapInsets: method essentially handle that for us?
  • Maurizio
    Maurizio about 11 years
    @avelis yes you have to provide both 1x and 2x images.
  • iwasrobbed
    iwasrobbed almost 11 years
    @avelis As always, you specify the name of the standard image.png image and then the OS takes care of swapping it out for [email protected] if necessary.
  • Craig B
    Craig B almost 11 years
    Very Nice! Couple things. Looks like you forgot to set the middle for the case where both segments are selected. Also, you can set the title attributes as well if you want customized font/colors/shadows/etc [[UISegmentedControl appearance] setTitleTextAttributes:...].
  • Ranjit
    Ranjit almost 11 years
    Hi @iWasRobbed, I want to have different images for pressed and unpressed state and also the images will be different for each segment. Can we achieve this by using above code.