简体   繁体   中英

iOS: How Do I Access Individual Segments in A UISegmentedControl?

The question is fairly straightforward, but I don't see anything that actually answers my need in the many UISegmentedControl posts:

ENVIRONMENT: I have a set of directories that are displayed in a UISegmentedControl. It's a flat hierarchy, with few directories, so this is the best way to display them.

Selecting a segment fills a UITableView below with the contents of that directory.

I can select a given segment programmatically, so that I can have the appropriate one selected as necessary.

Works great.

PROBLEM:

One of the directories is the "default" directory, and it will contain a mix of items already present, and new ones.

I'd like to badge the segment, so that there is an indicator of how many new ones are in it, so that folks know to select it, if it has not already been selected for them.

That means that I need to access the actual subviews and whatnot in that UISegmentedControl.

Not so easy. Creating a badge is child's play. Figuring out where to put the badge is grown-up stuff.

It looks like Apple is deliberately hiding direct access to segments. You can only affect the entire control.

Does anyone have any suggestions as to how I can modify just one segment, or even find out where that segment is?

The widthForSegmentAtIndex: . function appears to be worthless, as it is arbitrary as to whether or not it will give you any useful information.

No, the value returned by widthForSegmentAtIndex: is not arbitrary. As you can see in the documentation it returns either the width of the segment or 0.0, which means the segment is autosized.

There is a way to get the frame of each segment.

  1. Get the width of the UISegmentedControl
  2. ask each segment for its width
    • if 0.0 is returned increase a counter
    • else remove the width of the segment from the total width
  3. divide the remaining width by the number of auto sized segments and you have the size of each autosized segment.
  4. you can now use this information to calculate the frame of each segment.

Or in code:

As far as I have seen on iOS7 the "borders" between the segments are not part of the segments width.

CGFloat autosizedWidth = CGRectGetWidth(self.segment.bounds);
// iOS7 only?!
autosizedWidth -= (self.segment.numberOfSegments - 1); // ignore the 1pt. borders between segments


NSInteger numberOfAutosizedSegmentes = 0;
NSMutableArray *segmentWidths = [NSMutableArray arrayWithCapacity:self.segment.numberOfSegments];
for (NSInteger i = 0; i < self.segment.numberOfSegments; i++) {
    CGFloat width = [self.segment 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);
for (NSInteger i = 0; i < [segmentWidths count]; i++) {
    id width = segmentWidths[i];
    if (width == [NSNull null]) {
        [segmentWidths replaceObjectAtIndex:i withObject:@(autoWidth)];
    }
}

CGFloat x = CGRectGetMinX(self.segment.frame);
for (NSInteger i = 0; i < [segmentWidths count]; i++) {
    NSNumber *width = segmentWidths[i];
    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(x, CGRectGetMaxY(self.segment.frame) + 1, [width floatValue], 30)];
    view.backgroundColor = [UIColor colorWithHue:i/(float)[segmentWidths count] saturation:1 brightness:1 alpha:1];
    [self.view addSubview:view];
    x = CGRectGetMaxX(view.frame)+1;
}

This yields the following result:

在此输入图像描述

I would suggest you don't add your badge as subview of the UISegmentedControl, you can add it to the superView of the segmentedControl instead. Your badge should basically be a sibling of the segmentedControl


And please file a enhancement request with Apple. They won't give us access to the individual subviews, but they might at least tell us the actual size of the segments.

You know the size of the segmentedControl and you know the number of segments. Just divide to get the width of each segment. They're all of the same width.

Then place your badge on top of the segmentedControl at the appropriate x position.

I did some more tweaking, and I STILL don't have it perfect, but this seems to give a good approximation, give or take a pixel. 我做了一些调整,我仍然没有完美,但这似乎给了一个很好的近似,给或像素。

As long as the control is small, then the above works well, but there is some error. Probably because I am not taking in a couple of pixels worth of size, somewhere.

Here is what I did in my test app ( The project is updated to have more test controls). 项目已更新为具有更多测试控件)。

First, I declared a class extension on the UISegmentedControl class:

@interface UISegmentedControl (Overload)
- (NSArray*)mapUISegmentedControl;
@end

Then, I implement it, like so:

@implementation UISegmentedControl (Overload)
- (NSArray*)mapUISegmentedControl
{
    NSMutableArray  *segmentFrames = [NSMutableArray arrayWithCapacity:[self numberOfSegments]];
    NSInteger       numberOfAutosizedSegments = 0;
    double          ios7Coefficient = [[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0 ? 1 : 0;
    double          autosizedWidth = CGRectGetWidth ( [self bounds] ) - 1;

    for ( NSInteger i = 0; i < [self numberOfSegments]; i++ )
    {
        double width = [self widthForSegmentAtIndex:i];

        if ( width == 0.0f )
        {
            autosizedWidth -= 1;
            // If this is an auto-sized, we blank out the holding space for it.
            numberOfAutosizedSegments++;    // Add this to our count of auto-sized segments.
            [segmentFrames addObject:[NSNull null]];
        }
        else
        {
            // If the size has been explicitly set, we use that, and create a simple rect.
            autosizedWidth -= (width + ios7Coefficient);
            CGRect  frame = CGRectMake ( 0, 0, width, CGRectGetHeight ( [self bounds] ) );
            [segmentFrames addObject:[NSValue valueWithCGRect:frame]];
        }

    }

    // We divvy up the leftover space for the autoscales.
    double autoWidth = (autosizedWidth + ios7Coefficient) / (double)numberOfAutosizedSegments;

    double  x = 0;

    for ( NSInteger i = 0; i < [segmentFrames count]; i++ )
    {
        CGRect  frame = CGRectZero;
        double  width = 0;

        // If this is auto-sized (flagged by a null object), then make an autosized rect.
        if ( segmentFrames[i] == [NSNull null] )
        {
            width = ceil ( autoWidth - ios7Coefficient );
        }
        else    // Otherwise, use the rect they supplied.
        {
            width = CGRectGetWidth ( [(NSValue*)[segmentFrames objectAtIndex:i] CGRectValue] );
        }

        width += 1;

        frame = CGRectMake ( x, 0, width, CGRectGetHeight( [self bounds] ) );
        [segmentFrames replaceObjectAtIndex:i
                                 withObject:[NSValue valueWithCGRect:frame]
         ];

        // The x origin keeps up with the control.
        x += width;
    }

    return [NSArray arrayWithArray:segmentFrames];
}
@end

Next, I call that in the layout response, like so:

@implementation DemoViewController

- (void)drawOverlays
{
    NSArray *segmentMap = [[self segmentedControl] mapUISegmentedControl];

    if ( ![self colorBand] )
    {
        _colorBand = [[NSMutableArray alloc] init];
    }
    else
    {
        for ( UIView *view in [self colorBand] )
        {
            [view removeFromSuperview];
        }
        [[self colorBand] removeAllObjects];
    }

    for ( NSInteger i = 0; i < [segmentMap count]; i++ )
    {
        CGRect  frame = [(NSValue*)[segmentMap objectAtIndex:i] CGRectValue];
        frame.size.height /= 2.0;
        frame.origin.y = [[self segmentedControl] frame].origin.y + [[self segmentedControl] frame].size.height;
        UIView *view = [[UIView alloc] initWithFrame:frame];
        view.backgroundColor = [UIColor colorWithHue:i/(float)[segmentMap count] saturation:1 brightness:1 alpha:0.5];
        [[self view] addSubview:view];
        [[self colorBand] addObject:view];
    }
}

- (void)viewDidLayoutSubviews
{
    [self drawOverlays];
}
@end

This works in both iOS 6 and 7, but there seem to be occasional half-pixel errors, from time to time.

The results are shown below:

iOS6的

IOS 7

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM