Fixed labels in the selection bar of a UIPickerView

前端 未结 12 1183
一生所求
一生所求 2020-11-27 12:17

In the clocks application, the timer screen shows a picker (probably a UIPicker in UIDatePickerModeCountDownTimer mode) with some text in the selec

相关标签:
12条回答
  • 2020-11-27 12:35

    Embossing is explained here: Adding Emboss to a UILabel in a navigationItem.titleView (as seen with navigationItem.title)

    0 讨论(0)
  • 2020-11-27 12:36

    I've turned dizy's answer into a category on UIPickerView a couple years ago. Just verified that it still works with iOS SDK 4.3, and posting it here. It allows you to add a label (XX hours) and animate changes to this label (e.g. 1 hour -> 3 hours) just like UIDatePicker does.

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

    And the implementation:

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

    Usage example (in a view controller):

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

    Enjoy!

    0 讨论(0)
  • 2020-11-27 12: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.

    0 讨论(0)
  • 2020-11-27 12:42

    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

    0 讨论(0)
  • 2020-11-27 12:49

    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

    0 讨论(0)
  • 2020-11-27 12:52

    Create your picker, create a label with a shadow, and push it to a picker's subview below the selectionIndicator view.

    It would look something like this

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

    -- Pay it forward

    0 讨论(0)
提交回复
热议问题