Fixed labels in the selection bar of a UIPickerVie

2019-01-05 10:15发布

In the clocks application, the timer screen shows a picker (probably a UIPicker in UIDatePickerModeCountDownTimer mode) with some text in the selection bar ("hours" and "mins" in this case).

(edit) Note that these labels are fixed: They don't move when the picker wheel is rolling.

Is there a way to show such fixed labels in the selection bar of a standard UIPickerView component?

I did not find any API that would help with that. A suggestion was to add a UILabel as a subview of the picker, but that didn't work.


Answer

I followed Ed Marty's advice (answer below), and it works! Not perfect but it should fool people. For reference, here's my implementation, feel free to make it better...

- (void)viewDidLoad {
    // Add pickerView
    self.pickerView = [[UIPickerView alloc] initWithFrame:CGRectZero];
    [pickerView release];
    CGSize pickerSize = [pickerView sizeThatFits:CGSizeZero];
    CGRect screenRect = [[UIScreen mainScreen] applicationFrame];
    #define toolbarHeight           40.0
    CGFloat pickerTop = screenRect.size.height - toolbarHeight - pickerSize.height;
    CGRect pickerRect = CGRectMake(0.0, pickerTop, pickerSize.width, pickerSize.height);
    pickerView.frame = pickerRect;

    // Add label on top of pickerView
    CGFloat top = pickerTop + 2;
    CGFloat height = pickerSize.height - 2;
    [self addPickerLabel:@"x" rightX:123.0 top:top height:height];
    [self addPickerLabel:@"y" rightX:183.0 top:top height:height];
    //...
}

- (void)addPickerLabel:(NSString *)labelString rightX:(CGFloat)rightX top:(CGFloat)top height:(CGFloat)height {
#define PICKER_LABEL_FONT_SIZE 18
#define PICKER_LABEL_ALPHA 0.7
    UIFont *font = [UIFont boldSystemFontOfSize:PICKER_LABEL_FONT_SIZE];
    CGFloat x = rightX - [labelString sizeWithFont:font].width;

    // White label 1 pixel below, to simulate embossing.
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(x, top + 1, rightX, height)];
    label.text = labelString;
    label.font = font;
    label.textColor = [UIColor whiteColor];
    label.backgroundColor = [UIColor clearColor];
    label.opaque = NO;
    label.alpha = PICKER_LABEL_ALPHA;
    [self.view addSubview:label];
    [label release];

    // Actual label.
    label = [[UILabel alloc] initWithFrame:CGRectMake(x, top, rightX, height)];
    label.text = labelString;
    label.font = font;
    label.backgroundColor = [UIColor clearColor];
    label.opaque = NO;
    label.alpha = PICKER_LABEL_ALPHA;
    [self.view addSubview:label];
    [label release];
}

11条回答
SAY GOODBYE
2楼-- · 2019-01-05 10:32

I received an answer that works well in iOS 7 to my question, which is a pretty cool trick.

The idea is to create multiple components, and for those label components, specify that it's a single row. For the embossed look that some people have, you can return NSAttributedStrings with the delegate method:

- (NSAttributedString *)pickerView:(UIPickerView *)pickerView attributedTitleForRow:(NSInteger)row forComponent:(NSInteger)component

查看更多
Luminary・发光体
3楼-- · 2019-01-05 10:32

To re-create the embossed look for the labels...just create a image with the Text, so that you can easily apply a very similar effect to the text...and then use UIImageViews instead of Labels

查看更多
小情绪 Triste *
4楼-- · 2019-01-05 10:34

Can you show where you define pickerTop and pickerSize?

    CGFloat pickerTop = timePicker.bounds.origin.y;
CGSize pickerSize = timePicker.bounds.size;

That is what I have, but pickerTop seems to be wrong.

mike

查看更多
姐就是有狂的资本
5楼-- · 2019-01-05 10:36

Let's say we want to implement a picker view for selecting distance, there are 2 columns, one for distance, one for unit, which is km. Then we want the second column to be fixed. We can make it through some delegate methods.

- (NSString*)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
    if (component == 0) {
        return self.distanceItems[row];
    }
    else {
        return @"km";
    }
}

-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
    return 2;
}

-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
    if (component == 0) {
        return [self.distanceItems count];
    }
    else {
    // when it comes to the second column, only one row.
        return 1;
    }
}

Now we have this: enter image description here

I guess this is the simplest way.

查看更多
Animai°情兽
6楼-- · 2019-01-05 10:38

I also faced same problem. You can see working example in my custom made time picker published on GitHub:
https://github.com/kgadzinowski/iOSSecondsTimerPicker
It does exactly what you want.

查看更多
时光不老,我们不散
7楼-- · 2019-01-05 10:44

There are 2 things you can do:

If each row and component in row is a simple text, than you can simply use the default UIPickerView implementation as is, and in your controller implement the following UIPickerViewDelegate methods :

  • - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component to keep track of which row is selected

  • and return a different text for the selected row in your implementation of - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component

If you need to have something other than text as the differentiator for the selected row, than you basically need to create your own CustomPickerView that derives from the UIPickerView and then

  • First implement the - (void)selectRow:(NSInteger)row inComponent:(NSInteger)component animated:(BOOL)animated and keep track of which row is selected.

  • Then implement the - (UIView *)viewForRow:(NSInteger)row forComponent:(NSInteger)component to generate a different view for the selected row.

A sample for using the UIPickerView or implementing custom UIPickerView is available in the SDK, called UICatalog.

查看更多
登录 后发表回答