简体   繁体   English

iOS:如何在UISegmentedControl中访问单个段?

[英]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: 问题相当简单,但在许多UISegmentedControl帖子中我没有看到任何实际满足我需求的内容:

ENVIRONMENT: I have a set of directories that are displayed in a UISegmentedControl. 环境:我有一组显示在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. 选择一个段将使用该目录的内容填充下面的UITableView。

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. 这意味着我需要访问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: . 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. 不, widthForSegmentAtIndex:返回的值不是任意的。 As you can see in the documentation it returns either the width of the segment or 0.0, which means the segment is autosized. 正如您在文档中看到的那样,它返回段的宽度或0.0,这意味着段是自动调整的。

There is a way to get the frame of each segment. 有一种方法可以获得每个细分的框架。

  1. Get the width of the UISegmentedControl 获取UISegmentedControl的宽度
  2. ask each segment for its width 询问每个细分的宽度
    • if 0.0 is returned increase a counter 如果返回0.0,则增加一个计数器
    • 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. 据我所知,在iOS7上,各段之间的“边界”不是段宽度的一部分。

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. 我建议你不要将你的徽章添加为UISegmentedControl的子视图,你可以将它添加到segmentedControl的superView中。 Your badge should basically be a sibling of the segmentedControl 你的徽章基本上应该是segmentedControl的兄弟


And please file a enhancement request with Apple. 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. 你知道segmentedControl的大小,你知道段的数量。 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. 然后将徽章放在segmentedControl顶部的适当x位置。

UPDATE: 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 ( NOTE: The project is updated to have more test controls). 这是我在测试应用程序中所做的( 注意:项目已更新为具有更多测试控件)。

First, I declared a class extension on the UISegmentedControl class: 首先,我在UISegmentedControl类上声明了一个类扩展:

@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. 这适用于iOS 6和7,但似乎偶尔会出现半像素错误。

The results are shown below: 结果如下所示:

iOS6的

IOS 7

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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