34

在时钟应用程序中,计时器屏幕显示一个选择器(可能是UIPickerUIDatePickerModeCountDownTimer模式下),在选择栏中带有一些文本(在本例中为“小时”和“分钟”)。

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

有没有办法在标准UIPickerView组件的选择栏中显示这样的固定标签?

我没有找到任何有助于解决此问题的 API。一个建议是添加 aUILabel作为选择器的子视图,但这不起作用。


回答

我听从了 Ed Marty 的建议(下面的答案),它奏效了!不完美,但它应该愚弄人们。作为参考,这是我的实现,请随时改进...

- (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];
}
4

12 回答 12

14

创建您的选择器,创建一个带有阴影的标签,然后将其推送到 selectionIndicator 视图下方的选择器子视图。

它看起来像这样


UILabel *label = [[[UILabel alloc] initWithFrame:CGRectMake(135, 93, 80, 30)] autorelease];
label.text = @"Label";
label.font = [UIFont boldSystemFontOfSize:20];
label.backgroundColor = [UIColor clearColor];
label.shadowColor = [UIColor whiteColor];
label.shadowOffset = CGSizeMake (0,1);
[picker insertSubview:label aboveSubview:[picker.subviews objectAtIndex:5]]; 
//When you have multiple components (sections)...
//you will need to find which subview you need to actually get under
//so experiment with that 'objectAtIndex:5'
//
//you can do something like the following to find the view to get on top of
// define @class UIPickerTable;
// NSMutableArray *tables = [[NSMutableArray alloc] init];
// for (id i in picker.subviews) if([i isKindOfClass:[UIPickerTable class]]) [tables addObject:i];
// etc...

-- 提前付款

于 2009-03-05T20:38:12.127 回答
11

UIPickerView几年前,我已经将 dizy 的答案变成了一个类别。刚刚验证它仍然适用于 iOS SDK 4.3,并在此处发布。它允许您添加一个标签(XX 小时)并对这个标签进行动画更改(例如 1 小时 -> 3 小时),就像这样UIDatePicker做一样。

// UIPickerView_SelectionBarLabelSupport.h
//
// This file adds a new API to UIPickerView that allows to easily recreate
// the look and feel of UIDatePicker labeled components.
//
// Copyright (c) 2009, Andrey Tarantsov <andreyvit@gmail.com>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.


#import <Foundation/Foundation.h>


// useful constants for your font size-related code
#define kPickerViewDefaultTitleFontSize 20.0f
#define kDatePickerTitleFontSize 25.0f
#define kDatePickerLabelFontSize 21.0f


@interface UIPickerView (SelectionBarLabelSupport)

// The primary API to add a label to the given component.
// If you want to match the look of UIDatePicker, use 21pt as pointSize and 25pt as the font size of your content views (titlePointSize).
// (Note that UIPickerView defaults to 20pt items, so you need to use custom views. See a helper method below.)
// Repeated calls will change the label with an animation effect similar to UIDatePicker's one.
//
// To call this method on viewDidLoad, please call [pickerView layoutSubviews] first so that all subviews
// get created.
- (void)addLabel:(NSString *)label ofSize:(CGFloat)pointSize toComponent:(NSInteger)component leftAlignedAt:(CGFloat)offset baselineAlignedWithFontOfSize:(CGFloat)titlePointSize;

// A helper method for your delegate's "pickerView:viewForRow:forComponent:reusingView:".
// Creates a propertly positioned right-aligned label of the given size, and also handles reuse.
// The actual UILabel is a child of the returned view, use [returnedView viewWithTag:1] to retrieve the label.
- (UIView *)viewForShadedLabelWithText:(NSString *)label ofSize:(CGFloat)pointSize forComponent:(NSInteger)component rightAlignedAt:(CGFloat)offset reusingView:(UIView *)view;

// Creates a shaded label of the given size, looking similar to the labels used by UIPickerView/UIDatePicker.
- (UILabel *)shadedLabelWithText:(NSString *)label ofSize:(CGFloat)pointSize;

@end

和实施:

// UIPickerView_SelectionBarLabelSupport.m
//
// This file adds a new API to UIPickerView that allows to easily recreate
// the look and feel of UIDatePicker labeled components.
//
// Copyright (c) 2009, Andrey Tarantsov <andreyvit@gmail.com>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

#import "UIPickerView_SelectionBarLabelSupport.h"


// used to find existing component labels among UIPicker's children
#define kMagicTag 89464534
// a private UIKit implementation detail, but we do degrade gracefully in case it stops working
#define kSelectionBarClassName @"_UIPickerViewSelectionBar"

// used to sort per-component selection bars in a left-to-right order
static NSInteger compareViews(UIView *a, UIView *b, void *context) {
    CGFloat ax = a.frame.origin.x, bx = b.frame.origin.x;
    if (ax < bx)
        return -1;
    else if (ax > bx)
        return 1;
    else
        return 0;
}


@implementation UIPickerView (SelectionBarLabelSupport)

- (UILabel *)shadedLabelWithText:(NSString *)label ofSize:(CGFloat)pointSize {
    UIFont *font = [UIFont boldSystemFontOfSize:pointSize];
    CGSize size = [label sizeWithFont:font];
    UILabel *labelView = [[[UILabel alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)] autorelease];
    labelView.font = font;
    labelView.adjustsFontSizeToFitWidth = NO;
    labelView.shadowOffset = CGSizeMake(1, 1);
    labelView.textColor = [UIColor blackColor];
    labelView.shadowColor = [UIColor whiteColor];
    labelView.opaque = NO;
    labelView.backgroundColor = [UIColor clearColor];
    labelView.text = label;
    labelView.userInteractionEnabled = NO;
    return labelView;
}

- (UIView *)viewForShadedLabelWithText:(NSString *)title ofSize:(CGFloat)pointSize forComponent:(NSInteger)component rightAlignedAt:(CGFloat)offset reusingView:(UIView *)view {
    UILabel *label;
    UIView *wrapper;
    if (view != nil) {
        wrapper = view;
        label = (UILabel *)[wrapper viewWithTag:1];
    } else {
        CGFloat width = [self.delegate pickerView:self widthForComponent:component];

        label = [self shadedLabelWithText:title ofSize:pointSize];
        CGSize size = label.frame.size;
        label.frame = CGRectMake(0, 0, offset, size.height);
        label.tag = 1;
        label.textAlignment = UITextAlignmentRight;
        label.autoresizingMask = UIViewAutoresizingFlexibleHeight;

        wrapper = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, width, size.height)] autorelease];
        wrapper.autoresizesSubviews = NO;
        wrapper.userInteractionEnabled = NO;
        [wrapper addSubview:label];
    }
    label.text = title;
    return wrapper;
}

- (void)addLabel:(NSString *)label ofSize:(CGFloat)pointSize toComponent:(NSInteger)component leftAlignedAt:(CGFloat)offset baselineAlignedWithFontOfSize:(CGFloat)titlePointSize {
    NSParameterAssert(component < [self numberOfComponents]);

    NSInteger tag = kMagicTag + component;
    UILabel *oldLabel = (UILabel *) [self viewWithTag:tag];
    if (oldLabel != nil && [oldLabel.text isEqualToString:label])
        return;

    NSInteger n = [self numberOfComponents];
    CGFloat total = 0.0;
    for (int c = 0; c < component; c++)
        offset += [self.delegate pickerView:self widthForComponent:c];
    for (int c = 0; c < n; c++)
        total += [self.delegate pickerView:self widthForComponent:c];
    offset += (self.bounds.size.width - total) / 2;

    offset += 2 * component; // internal UIPicker metrics, measured on a screenshot
    offset += 4; // add a gap

    CGFloat baselineHeight = [@"X" sizeWithFont:[UIFont boldSystemFontOfSize:titlePointSize]].height;
    CGFloat labelHeight = [@"X" sizeWithFont:[UIFont boldSystemFontOfSize:pointSize]].height;

    UILabel *labelView = [self shadedLabelWithText:label ofSize:pointSize];
    labelView.frame = CGRectMake(offset,
                                 (self.bounds.size.height - baselineHeight) / 2 + (baselineHeight - labelHeight) - 1,
                                 labelView.frame.size.width,
                                 labelView.frame.size.height);
    labelView.tag = tag;

    UIView *selectionBarView = nil;
    NSMutableArray *selectionBars = [NSMutableArray array];
    for (UIView *subview in self.subviews) {
        if ([[[subview class] description] isEqualToString:kSelectionBarClassName])
            [selectionBars addObject:subview];
    }
    if ([selectionBars count] == n) {
        [selectionBars sortUsingFunction:compareViews context:NULL];
        selectionBarView = [selectionBars objectAtIndex:component];
    }
    if (oldLabel != nil) {
        [UIView beginAnimations:nil context:oldLabel];
        [UIView setAnimationDuration:0.25];
        [UIView setAnimationDelegate:self];
        [UIView setAnimationDidStopSelector:@selector(YS_barLabelHideAnimationDidStop:finished:context:)];
        oldLabel.alpha = 0.0f;
        [UIView commitAnimations];
    }
    // if the selection bar hack stops working, degrade to using 60% alpha
    CGFloat normalAlpha = (selectionBarView == nil ? 0.6f : 1.0f);
    if (selectionBarView != nil)
        [self insertSubview:labelView aboveSubview:selectionBarView];
    else
        [self addSubview:labelView];
    if (oldLabel != nil) {
        labelView.alpha = 0.0f;
        [UIView beginAnimations:nil context:oldLabel];
        [UIView setAnimationDuration:0.25];
        [UIView setAnimationDelay:0.25];
        labelView.alpha = normalAlpha;
        [UIView commitAnimations];
    } else {
        labelView.alpha = normalAlpha;
    }
}

- (void)YS_barLabelHideAnimationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(UIView *)oldLabel {
    [oldLabel removeFromSuperview];
}

@end

使用示例(在视图控制器中):

- (void)updateFloorLabel {
    NSInteger floor = [self.pickerView numberOfRowsInComponent:0] - [self.pickerView selectedRowInComponent:0];
    NSString *suffix = @"th";
    if (((floor % 100) / 10) != 1) {
        switch (floor % 10) {
            case 1:  suffix = @"st"; break;
            case 2:  suffix = @"nd"; break;
            case 3:  suffix = @"rd"; break;
        }
    }
    [self.pickerView addLabel:[NSString stringWithFormat:@"%@ Floor", suffix]
                       ofSize:21
                  toComponent:0
                leftAlignedAt:50
baselineAlignedWithFontOfSize:25];    
}

- (void)viewDidLoad {
  ...
  [self.pickerView layoutSubviews];
  [self updateFloorLabel];
  ...
}

- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view {
    NSString *s = [NSString stringWithFormat:@"%d", [pickerView numberOfRowsInComponent:0] - row];
    return [pickerView viewForShadedLabelWithText:s ofSize:25 forComponent:0 rightAlignedAt:46 reusingView:view];
}

- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
    [self updateFloorLabel];
}

享受!

于 2011-05-29T18:43:34.010 回答
7

假设我们要实现一个选择距离的选择器视图,有2列,一列距离,一列单位,即km。然后我们希望修复第二列。我们可以通过一些委托方法来实现。

- (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;
    }
}

现在我们有了这个: 在此处输入图像描述

我想这是最简单的方法。

于 2014-04-13T21:20:00.783 回答
4

你可以做两件事:

如果每一行和每一行中的组件都是一个简单的文本,那么您可以简单地使用默认的 UIPickerView 实现,并在您的控制器中实现以下UIPickerViewDelegate方法:

  • - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component跟踪选择了哪一行

  • 并在您的实施中为所选行返回不同的文本- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component

如果您需要将文本以外的其他内容作为所选行的差异化因素,那么您基本上需要创建自己CustomPickerView的内容UIPickerView,然后

  • 首先实现- (void)selectRow:(NSInteger)row inComponent:(NSInteger)component animated:(BOOL)animated并跟踪选择了哪一行。

  • 然后实现- (UIView *)viewForRow:(NSInteger)row forComponent:(NSInteger)component为所选行生成不同的视图。

SDK 中提供了使用 UIPickerView 或实现自定义 UIPickerView 的示例,称为 UICatalog。

于 2008-12-15T08:23:31.077 回答
3

我收到了一个在 iOS 7 中对我的问题运行良好的答案这是一个非常酷的技巧

这个想法是创建多个组件,并且对于那些标签组件,指定它是单行。对于某些人的浮雕外观,您可以使用委托方法返回 NSAttributedStrings:

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

于 2013-11-07T18:31:54.250 回答
1

与其在 UIPickerView 中添加标签,不如将其粘贴在其顶部,作为与其重叠的兄弟。唯一有问题的是如何获得相同的字体。我不知道如何获得那种浮雕外观,但也许其他人会这样做,在这种情况下,这根本不是问题。

于 2009-01-06T20:29:53.483 回答
1

要重新创建标签的浮雕外观...只需使用文本创建图像,以便您可以轻松地将非常相似的效果应用于文本...然后使用 UIImageViews 而不是标签

于 2009-03-06T11:11:03.040 回答
1

我也面临同样的问题。您可以在我在 GitHub 上发布的自定义时间选择器中看到工作示例:
https
://github.com/kgadzinowski/iOSSecondsTimerPicker 它完全符合您的要求。

于 2013-09-03T12:59:49.553 回答
0

你能展示你定义pickerTop和pickerSize的地方吗?

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

这就是我所拥有的,但 pickerTop 似乎是错误的。

麦克风

于 2009-02-13T23:30:36.943 回答
0

这里解释了浮雕:Adding Emboss to a UILabel in a navigationItem.titleView (as seen with navigationItem.title)

于 2010-03-29T11:17:10.117 回答
0

请注意,xib 编辑器也允许添加子视图,因此您可以避免使用过多的编码和尺寸猜测。

于 2011-05-27T00:51:29.787 回答
0

水平对齐多个选择器,它们之间没有空格,并关闭静态列的用户交互。

在每个选取器上设置一个标签,以确定哪个是数据源调用中的选取器视图。静态列的行数显然为 1。

这也可以更好地控制宽度和位置。

于 2020-02-22T16:10:38.963 回答