Marcações fixas na barra de seleção de um UIPickerView

No aplicativo de relógios, a canvas do timer mostra um selecionador (provavelmente um UIPicker no modo UIDatePickerModeCountDownTimer ) com algum texto na barra de seleção (“horas” e “min” neste caso).

(edit) Note que estes labels são fixos : Eles não se movem quando o seletor está rolando.

Existe uma maneira de mostrar esses labels fixos na barra de seleção de um componente UIPickerView padrão?

Não encontrei nenhuma API que ajudasse nisso. Uma sugestão era adicionar um UILabel como uma subvisualização do selecionador, mas isso não funcionou.


Responda

Segui o conselho de Ed Marty (resposta abaixo) e funciona! Não é perfeito, mas deve enganar as pessoas. Para referência, aqui está minha implementação, fique à vontade para melhorar …

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

Crie seu selecionador, crie um label com uma sombra e empurre-o para a subvisualização de um selecionador abaixo da visualização selectionIndicator.

Seria algo parecido com isto

 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... 

— Pagar adiantado

Transformei a resposta do dizy em uma categoria no UIPickerView alguns anos atrás. Apenas verifiquei que ainda funciona com o iOS SDK 4.3 e publique-o aqui. Ele permite que você adicione um label (XX horas) e anime as alterações para esse label (por exemplo, 1 hora -> 3 horas), assim como faz o 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  // // 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  // 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 

E a implementação:

 // 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  // // 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 

Exemplo de uso (em um controlador de visualização):

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

Apreciar!

Digamos que queremos implementar uma visualização do selecionador para selecionar a distância, há duas colunas, uma para a distância, uma para a unidade, que é km. Então queremos que a segunda coluna seja corrigida. Podemos passar por alguns methods delegates.

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

Agora nós temos isso: insira a descrição da imagem aqui

Eu acho que esse é o jeito mais simples.

Existem duas coisas que você pode fazer:

Se cada linha e componente na linha for um texto simples, você pode simplesmente usar a implementação UIPickerView padrão como está e no controlador implementar os seguintes methods UIPickerViewDelegate :

  • - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component para acompanhar qual linha está selecionada

  • e retornar um texto diferente para a linha selecionada na implementação de - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component

Se você precisa ter algo diferente de texto como o diferenciador para a linha selecionada, então basicamente você precisa criar seu próprio CustomPickerView que deriva do UIPickerView e então

  • Primeiro, implemente a linha - (void)selectRow:(NSInteger)row inComponent:(NSInteger)component animated:(BOOL)animated e acompanhe qual linha está selecionada.

  • Em seguida, implemente o - (UIView *)viewForRow:(NSInteger)row forComponent:(NSInteger)component para gerar uma exibição diferente para a linha selecionada.

Uma amostra para usar o UIPickerView ou implementar o UIPickerView personalizado está disponível no SDK, chamado UICatalog.

Recebi uma resposta que funciona bem no iOS 7 para a minha pergunta , que é um truque muito legal.

A ideia é criar vários componentes e, para esses componentes de etiqueta, especificar que é uma única linha. Para a aparência em relevo que algumas pessoas têm, você pode retornar NSAttributedStrings com o método delegate:

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

Em vez de adicionar um label dentro do UIPickerView, basta colocá-lo em cima dele, como um irmão que se sobrepõe a ele. A única coisa que é problemática é como obter a mesma fonte. Eu não sei como obter essa aparência em alto relevo, mas talvez outra pessoa, nesse caso, não é realmente um problema em tudo.

Para recriar a aparência em relevo das etiquetas … basta criar uma imagem com o texto, para que você possa aplicar facilmente um efeito muito semelhante ao texto … e, em seguida, usar UIImageViews em vez de labels

Eu também enfrentei o mesmo problema. Você pode ver o exemplo de trabalho no meu selecionador de tempo personalizado publicado no GitHub:
https://github.com/kgadzinowski/iOSSecondsTimerPicker
Faz exatamente o que você quer.

Você pode mostrar onde você define pickerTop e pickerSize?

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

Isso é o que eu tenho, mas pickerTop parece estar errado.

Mike

A gravação é explicada aqui: Adicionando Emboss a um UILabel em um navigationItem.titleView (como visto em navigationItem.title)

Note que o editor xib permite também adicionar visualizações de crianças, para que você evite usar muita codificação e adivinhação em dimensões.