Get frame from certain UISegmentedControl index?

2019-02-07 06:50发布

问题:

In my app, I use a UIPopoverController and I use the presentPopoverFromRect API. What I am doing now is just setting it to the frame of my whole UISegmentedControl. However I want to be more precise than this. Is there any way to get the frame of a specific index in the UISegmentedControl?

Thanks!

回答1:

If the segments are equal, why not just divide the width of the control by the number of the selected segment (+1 because numbering starts at 0)? EDIT: Like this

-(void)showPopover:(id)sender {
    if ((UISegmentedControl*)sender.selectedSegmentIndex == 0)
        [self.popover presentPopoverFromRect:CGRectMake(self.segmentedControl.frame.size.width/6, self.segmentedControl.frame.origin.y, aWidth, aHeight)]
}

It's over 6 (I'm assuming a 3 segment implementation), because you have to get the center of the segment, and 3 would put it on the lines. And if you do some simple math here (let's assume the whole control is 60 px wide), then 60/3 yeilds 20. Because each segment is 20 px wide, the width of 60 over six yields the correct answer 10.



回答2:

For our project we needed the actual frames of each segment, frame division wasn't enough. Here's a function I wrote that calculates the exact frame for every segment. Be aware that it accesses the segmented control actual subviews, so it might break in any iOS update.

- (CGRect)segmentFrameForIndex:(NSInteger)index inSegmentedControl:(UISegmentedControl *)control
{
    // WARNING: This function gets frame from UISegment objects, undocumented subviews of UISegmentedControl.
    // May break in iOS updates.

    NSMutableArray *segments = [NSMutableArray arrayWithCapacity:self.numberOfSegments];
    for (UIView *view in control.subviews) {
        if ([NSStringFromClass([view class]) isEqualToString:@"UISegment"]) {
            [segments addObject:view];
        }
    }

    NSArray *sorted = [segments sortedArrayUsingComparator:^NSComparisonResult(UIView *a, UIView *b) {
        if (a.frame.origin.x < b.frame.origin.x) {
            return NSOrderedAscending;
        } else if (a.frame.origin.x > b.frame.origin.x) {
            return NSOrderedDescending;
        }
        return NSOrderedSame;
    }];

    return [[sorted objectAtIndex:index] frame];
}


回答3:

This can be done much easier without matching class names, providing that you use no custom segmented control (Swift version)

extension NSSegmentedControl {

    func frameForSegment(segment: Int) -> NSRect {
        var left : CGFloat = 0
        for i in 0..<segmentCount {
            let w = widthForSegment(i)
            if i == segment {
                let off = CGFloat(i) + 2 // Account for separators and border.
                return NSRect(x: left + off, y: bounds.minY, width: w, height: bounds.height)
            }
            left += w
        }
        return NSZeroRect
    }
}


回答4:

If somebody is searching for a solution for NSSegmentedControl, I've slightly modified @erik-aigner's answer. For UISegmentedControl it should work similarly.

This version improves the geometry computation for spacing and the code in general. Disclaimer: It was tested only for a segmented control placed in a toolbar.

import AppKit

public extension NSSegmentedControl {

    /// The width of a single horizontal border.
    public static let horizontalBorderWidth: CGFloat = 3

    /// The height of a single vertical border.
    public static let verticalBorderWidth: CGFloat = 2

    /// The horizontal spacing between segments.
    public static let horizontalSegmentSpacing: CGFloat = 1

    /// Returns the frame of the specified segment.
    ///
    /// - Parameter segment: The index of the segment whose frame should be computed.
    /// - Returns: The frame of the segment or `.zero` if an invalid segment index is passed in.
    public func frame(forSegment segment: Int) -> NSRect {
        let y = bounds.minY - NSSegmentedControl.verticalBorderWidth
        let height = bounds.height
        var left = NSSegmentedControl.horizontalBorderWidth

        for index in 0..<segmentCount {
            let width = self.width(forSegment: index)
            if index == segment {
                return NSRect(x: left, y: y, width: width, height: height)
            }
            left += NSSegmentedControl.horizontalSegmentSpacing
            left += width
        }

        return .zero
    }

}