Created
October 10, 2021 17:10
-
-
Save hacknicity/fab6d709bb8f655f4f12fa5b9d58a421 to your computer and use it in GitHub Desktop.
UIPIckerView+SelectionBarLabelSupport
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ECTStepAddViewController.m | |
// EasyCookingTimer | |
// | |
// Created by Geoff Hackworth on 03/03/2012. | |
// Copyright (c) 2012 Hacknicity. All rights reserved. | |
// | |
#import "ECTStepAddViewController.h" | |
#import "ECTStepNameDetailTableCell.h" | |
#import "ECTStepNoteDetailTableCell.h" | |
#import "ECTStepDurationPickerController.h" | |
#import "ECTDataModelStep.h" | |
@interface ECTStepAddViewController () | |
@property (strong, nonatomic) ECTStepDurationPickerController *stepDurationPickerController; | |
@end | |
@implementation ECTStepAddViewController | |
#pragma mark - Initialisation and Destruction | |
- (id)init | |
{ | |
self = [super initWithNibName:@"ECTStepPropertiesView" | |
entity:[[ECTDataModelStep alloc] init]]; | |
if (self) | |
{ | |
self.stepDurationPickerController = [[ECTStepDurationPickerController alloc] init]; | |
} | |
return self; | |
} | |
- (ECTEntityNameDetailTableCellBase *)nameDetailTableCellForTableView:(UITableView *)tableView | |
{ | |
return [ECTStepNameDetailTableCell cellForTableView:tableView]; | |
} | |
- (ECTEntityNoteDetailTableCellBase *)noteDetailTableCellForTableView:(UITableView *)tableView | |
{ | |
return [ECTStepNoteDetailTableCell cellForTableView:tableView]; | |
} | |
#pragma mark - View Lifecycle | |
- (void)viewDidLoad | |
{ | |
[super viewDidLoad]; | |
self.pickerView.delegate = self.stepDurationPickerController; | |
self.pickerView.dataSource = self.stepDurationPickerController; | |
self.stepDurationPickerController.pickerView = self.pickerView; | |
self.stepDurationPickerController.duration = ((ECTDataModelStep *)self.entity).duration; | |
self.navigationItem.title = NSLocalizedString(@"Add Step", @"Title of Add Step view"); | |
// For iOS 7 we need to provide a background colour for the view because the date picker is mostly transparent | |
if (@available(iOS 13.0, *)) | |
{ | |
self.view.backgroundColor = [UIColor systemBackgroundColor]; | |
} | |
else | |
{ | |
self.view.backgroundColor = [UIColor whiteColor]; | |
} | |
// And the bottom filler view beneath the picker (only used in iPad) should match the table view background colour instead of being black (which matches the background colour of the picker on iOS 6 and earlier) | |
self.bottomFillerView.backgroundColor = self.tableView.backgroundColor; | |
} | |
- (void)viewWillAppear:(BOOL)animated | |
{ | |
[super viewWillAppear:animated]; | |
// Add static labels to the picker view (this doesn't work on iPad if I do it in viewDidLoad) | |
[self.pickerView layoutSubviews]; | |
[self.stepDurationPickerController addLabels]; | |
} | |
- (void)viewWillLayoutSubviews | |
{ | |
[super viewWillLayoutSubviews]; | |
// Position the picker in the bottom of the navigation controller's view, avoiding the toolbar and, on iPhone X, the Home Indicator. On iPad the picker is positioned higher up the view so that it remains visible in portrait when the keyboard is showing | |
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) | |
{ | |
CGSize pickerSize = [self.pickerView sizeThatFits:CGSizeZero]; | |
self.pickerView.frame = CGRectMake(0.0f, | |
self.view.frame.size.height - pickerSize.height - self.view.safeAreaInsets.bottom, | |
self.view.frame.size.width, | |
pickerSize.height); | |
// #36: ensure the table view's bottom is aligned with the picker view's top | |
CGRect tableViewFrame = self.tableView.frame; | |
tableViewFrame.size.height = CGRectGetMinY(self.pickerView.frame) - CGRectGetMinY(self.tableView.frame); | |
self.tableView.frame = tableViewFrame; | |
} | |
} | |
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator | |
{ | |
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) { | |
[self.stepDurationPickerController removeLabels]; | |
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) { | |
[self.stepDurationPickerController addLabels]; | |
}]; | |
} | |
#pragma mark - UI Logic | |
- (void)saveEntityProperties | |
{ | |
[super saveEntityProperties]; | |
// Just in case somebody manages to dismiss the dialog while the duration is set to 0, force it to 1 minute | |
NSUInteger duration = self.stepDurationPickerController.duration; | |
((ECTDataModelStep *)self.entity).duration = duration ? duration : 60; | |
} | |
#pragma mark - ECTEntityNameDetailTableCellBaseDelegate protocol | |
- (void)ECTEntityNameDetailTableCellDidBecomeFirstResponder:(ECTEntityNameDetailTableCellBase *)entityNameDetailTableCell | |
{ | |
// Ensure the name field is fully visible when focus is switched to it. This might be redundantly doing the same thing that ECTEntityNoteDetailTableCellDidResignFirstResponder: did (when moving focus from the note to the name whilst in portrait), but it's possible for the user to manually scroll and select the name. I want the name to be fully visible when they do that | |
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) | |
{ | |
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES]; | |
} | |
} | |
- (void)ECTEntityNameDetailTableCellDidPressReturn:(ECTEntityNameDetailTableCellBase *)entityNameDetailTableCell | |
{ | |
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) | |
{ | |
// On iPhone we need the return key to hide the keyboard instead of moving focus to the note field, so use resign the first responder status | |
[[self nameDetailTableCell] resignFirstResponder]; | |
} | |
else | |
{ | |
// Otherwise we take the defalt behaviour (moving focus to the note field) | |
[super ECTEntityNameDetailTableCellDidPressReturn:entityNameDetailTableCell]; | |
} | |
} | |
#pragma mark - ECTEntityNoteDetailTableCellBaseDelegate protocol | |
- (void)ECTEntityNoteDetailTableCellDidBecomeFirstResponder:(ECTEntityNoteDetailTableCellBase *)entityNoteDetailTableCell | |
{ | |
// The input accessory view on the keyboard overlaps the note field, so we need to scroll the table up to make the entire table cell visible. The scrolling only works because we've artificially (and invisibly) increased the height of the table view by having an empty second section. (Doing this on iPad has no effect since the cell is entirely visible anyway, but I want to make it clear I only need to do this on iPhone with the explicit check) | |
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) | |
{ | |
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES]; | |
} | |
} | |
- (void)ECTEntityNoteDetailTableCellDidResignFirstResponder:(ECTEntityNoteDetailTableCellBase *)entityNoteDetailTableCell | |
{ | |
// The input accessory view on the keyboard overlaps the note field, so we scrolled the table up to make the entire table cell visible when that field got focus. Now it is losing focus, we can scroll the name field back into place. BUT, we only do this in portrait since in landscape you can only see one field at once anyway and scrolling away from the note looks odd. (Doing this on iPad has no effect since the cell is entirely visible anyway, but I want to make it clear I only need to do this on iPhone with the explicit check) | |
UIScreen *screen = [UIScreen mainScreen]; | |
BOOL isPortrait = CGRectGetWidth(screen.bounds) < CGRectGetHeight(screen.bounds); | |
if (([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) && isPortrait) | |
{ | |
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES]; | |
} | |
} | |
- (void)ECTEntityNoteDetailTableCellDidFinishEditing:(ECTEntityNoteDetailTableCellBase *)entityNoteDetailTableCell | |
{ | |
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) | |
{ | |
// On iPhone we need to hide the keyboard instead of moving focus to the name field, so use resign the first responder status | |
[[self noteDetailTableCell] resignFirstResponder]; | |
} | |
else | |
{ | |
// Otherwise we take the defalt behaviour (moving focus to the name field) | |
[super ECTEntityNoteDetailTableCellDidFinishEditing:entityNoteDetailTableCell]; | |
} | |
} | |
#pragma mark - UITableViewDataSource methods | |
// This is a hack to make the table view occupy slightly more space than is needed for the name and note rows (though invisibly so), so there's enough content to allow us to scroll the note table cell to the top when it gets focus (so that the input accessory view on the keyboard does not overlap it). There's still not quite enough content to fully scroll to the top (using 3 gives that), but I prefer being able to see the bottom part of the name field so long as the entire note field is visible so settled on 2! | |
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView | |
{ | |
return 2; | |
} | |
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section | |
{ | |
return (section == 0) ? 2 : 0; | |
} | |
#pragma mark - UITableViewDelegate methods | |
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath | |
{ | |
if ((indexPath.row == 1) && [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) | |
{ | |
// We need to make a special case for the iPad's step properties because the space above the picker is smaller | |
return 4.0 * self.tableView.rowHeight; | |
} | |
else | |
{ | |
CGFloat height = [super tableView:tableView heightForRowAtIndexPath:indexPath]; | |
// We leave space in case the predictive text bar is visible, but avoid going below one row height (which would happen in landscape) | |
if (indexPath.row == 1) | |
{ | |
height = MAX(self.tableView.rowHeight, height - self.tableView.rowHeight); | |
} | |
return height; | |
} | |
} | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ECTStepDurationPickerController.h | |
// EasyCookingTimer | |
// | |
// Created by Geoff Hackworth on 30/05/2012. | |
// Copyright (c) 2012 Hacknicity. All rights reserved. | |
// | |
#import <Foundation/Foundation.h> | |
@interface ECTStepDurationPickerController : NSObject <UIPickerViewDelegate, UIPickerViewDataSource> | |
@property (strong, nonatomic) UIPickerView *pickerView; | |
@property (assign, nonatomic) NSUInteger duration; | |
// Designated initialiser | |
- (id)init; | |
- (void)addLabels; | |
- (void)removeLabels; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ECTStepDurationPickerController.m | |
// EasyCookingTimer | |
// | |
// Created by Geoff Hackworth on 30/05/2012. | |
// Copyright (c) 2012 Hacknicity. All rights reserved. | |
// | |
#import "ECTStepDurationPickerController.h" | |
#import "UIPickerView+SelectionBarLabelSupport.h" | |
#import "ECTDebugSettings.h" | |
#define kPickerLabelLeftAlignedAt 40.0f | |
#define kPickerTitleRightAlignedAt 45.0f | |
@interface ECTStepDurationPickerController () | |
@property (assign, nonatomic) NSUInteger hours; | |
@property (assign, nonatomic) NSUInteger minutes; | |
@property (assign, nonatomic) NSUInteger seconds; | |
+ (void)initialize; | |
+ (NSArray *)rowTitlesWithUpperBound:(NSUInteger)upperBound step:(NSUInteger)step; | |
@end | |
@implementation ECTStepDurationPickerController | |
static NSArray *pickerComponents; | |
#pragma mark - Initialisation and Destruction | |
+ (void)initialize | |
{ | |
if (self == [ECTStepDurationPickerController class]) | |
{ | |
pickerComponents = [[NSArray alloc] initWithObjects:[self rowTitlesWithUpperBound:24 step:1], | |
[self rowTitlesWithUpperBound:60 step:1], | |
[self rowTitlesWithUpperBound:60 step:5], | |
nil]; | |
} | |
} | |
+ (NSArray *)rowTitlesWithUpperBound:(NSUInteger)upperBound step:(NSUInteger)step | |
{ | |
NSMutableArray *rowTitles = [[NSMutableArray alloc] initWithCapacity:upperBound]; | |
for (NSUInteger title = 0; title < upperBound; title += step) | |
{ | |
// Space pad the labels to give one space left margin and right align single digits by adding an extra two spaces | |
[rowTitles addObject:[NSString stringWithFormat:@"%lu", (unsigned long)title]]; | |
} | |
return rowTitles; | |
} | |
// Designated initialiser | |
- (id)init | |
{ | |
return [super init]; | |
} | |
#pragma mark - View Lifecycle | |
- (void)addLabels | |
{ | |
[self.pickerView addLabel:NSLocalizedString(@"h", @"Label for step duration picker hours") | |
ofSize:kDatePickerLabelFontSize | |
toComponent:0 | |
leftAlignedAt:kPickerLabelLeftAlignedAt | |
baselineAlignedWithFontOfSize:kDatePickerTitleFontSize]; | |
[self.pickerView addLabel:NSLocalizedString(@"m", @"Label for step duration picker minutes") | |
ofSize:kDatePickerLabelFontSize | |
toComponent:1 | |
leftAlignedAt:kPickerLabelLeftAlignedAt | |
baselineAlignedWithFontOfSize:kDatePickerTitleFontSize]; | |
[self.pickerView addLabel:NSLocalizedString(@"s", @"Label for step duration picker seconds") | |
ofSize:kDatePickerLabelFontSize | |
toComponent:2 | |
leftAlignedAt:kPickerLabelLeftAlignedAt | |
baselineAlignedWithFontOfSize:kDatePickerTitleFontSize]; | |
} | |
- (void)removeLabels | |
{ | |
[self.pickerView removeAllLabels]; | |
} | |
#pragma mark - Properties | |
- (void)setDuration:(NSUInteger)duration | |
{ | |
self.hours = (duration / (60 * 60)); | |
self.minutes = (duration - self.hours * 60 * 60) / 60; | |
self.seconds = duration % 60; | |
} | |
- (NSUInteger)duration | |
{ | |
return (self.hours * 60 + self.minutes) * 60 + self.seconds; | |
} | |
- (NSUInteger)hours | |
{ | |
return [self.pickerView selectedRowInComponent:0]; | |
} | |
- (void)setHours:(NSUInteger)hours | |
{ | |
[self.pickerView selectRow:hours inComponent:0 animated:NO]; | |
} | |
- (NSUInteger)minutes | |
{ | |
return [self.pickerView selectedRowInComponent:1]; | |
} | |
- (void)setMinutes:(NSUInteger)minutes | |
{ | |
[self.pickerView selectRow:minutes inComponent:1 animated:NO]; | |
} | |
- (NSUInteger)seconds | |
{ | |
return [self.pickerView selectedRowInComponent:2] * 5; | |
} | |
- (void)setSeconds:(NSUInteger)seconds | |
{ | |
[self.pickerView selectRow:seconds / 5 inComponent:2 animated:NO]; | |
} | |
#pragma mark - UIPickerViewDataSource Protocol | |
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView | |
{ | |
return [pickerComponents count]; | |
} | |
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component | |
{ | |
return [[pickerComponents objectAtIndex:component] count]; | |
} | |
#pragma mark - UIPickerViewDelegate Protocol | |
- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component | |
{ | |
return 75.0; | |
} | |
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view | |
{ | |
return [pickerView viewForShadedLabelWithText:[[pickerComponents objectAtIndex:component] objectAtIndex:row] | |
ofSize:kDatePickerTitleFontSize | |
forComponent:component | |
rightAlignedAt:kPickerTitleRightAlignedAt | |
reusingView:view]; | |
} | |
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component | |
{ | |
// In testing I was able to scroll stuff around and not always receive a callback for each component. This made it possible to choose 0, 0, 0 (which I need to avoid). Grabbing the current values of _each_ component whenever _any_ component was selected seemed to work | |
if (self.minutes == 0 && self.seconds == 0 && self.hours == 0) | |
{ | |
// Setting the control directly allows us to use animation. I don't want to use animation in the properties because they are used on first load and you get a visual glitch if animation is used there | |
[self.pickerView selectRow:1 inComponent:1 animated:YES]; | |
} | |
} | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 <[email protected]> | |
// | |
// 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> | |
#import <UIKit/UIKit.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. | |
- (void)addLabel:(NSString *)label ofSize:(CGFloat)pointSize toComponent:(NSInteger)component leftAlignedAt:(CGFloat)offset baselineAlignedWithFontOfSize:(CGFloat)titlePointSize; | |
// Hacknicity Added: Remove a label | |
- (void)removeLabelForComponent:(NSInteger)component; | |
- (void)removeAllLabels; | |
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 <[email protected]> | |
// | |
// 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 | |
@implementation UIPickerView (SelectionBarLabelSupport) | |
- (UILabel *)shadedLabelWithText:(NSString *)label ofSize:(CGFloat)pointSize { | |
UIFont *font = [UIFont boldSystemFontOfSize:pointSize]; | |
CGSize size = [label sizeWithAttributes:@{NSFontAttributeName: font}]; | |
size.width = ceil(size.width); | |
size.height = ceil(size.height); | |
UILabel *labelView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)]; | |
#if !__has_feature(objc_arc) | |
[labelView autorelease]; | |
#endif | |
labelView.font = font; | |
labelView.adjustsFontSizeToFitWidth = NO; | |
if (@available(iOS 13.0, *)) { | |
labelView.textColor = [UIColor labelColor]; | |
} else { | |
labelView.textColor = [UIColor blackColor]; | |
} | |
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 = NSTextAlignmentRight; | |
label.autoresizingMask = UIViewAutoresizingFlexibleHeight; | |
wrapper = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, size.height)]; | |
#if !__has_feature(objc_arc) | |
[wrapper autorelease]; | |
#endif | |
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 += 5 * component; // internal UIPicker metrics, measured on a screenshot | |
offset += 4; // add a gap | |
CGFloat baselineHeight = ceil([@"X" sizeWithAttributes:@{NSFontAttributeName: [UIFont boldSystemFontOfSize:titlePointSize]}].height); | |
CGFloat labelHeight = ceil([@"X" sizeWithAttributes:@{NSFontAttributeName: [UIFont boldSystemFontOfSize:pointSize]}].height); | |
UILabel *labelView = [self shadedLabelWithText:label ofSize:pointSize]; | |
// On iOS 13 I noticed that something (which I cannot find in the view hierarchy!) is slightly changing the color of the selected row's picker labels so they are not quite labelColor. To avoid a near mismatch in colours with the extra labels, I deliberately choose secondaryLabelColor so they appear to be deliberately a different color. On closer inspection, this was also happening on iOS 12! | |
if (@available(iOS 13.0, *)) { | |
labelView.textColor = [UIColor secondaryLabelColor]; | |
} else { | |
labelView.textColor = [UIColor grayColor]; | |
} | |
labelView.frame = CGRectMake(offset, | |
(self.bounds.size.height - baselineHeight) / 2 + (baselineHeight - labelHeight) - 1, | |
labelView.frame.size.width, | |
labelView.frame.size.height); | |
labelView.tag = tag; | |
if (oldLabel != nil) { | |
#if !__has_feature(objc_arc) | |
[UIView beginAnimations:nil context:oldLabel]; | |
#else | |
[UIView beginAnimations:nil context:(__bridge void *)(oldLabel)]; | |
#endif | |
[UIView setAnimationDuration:0.25]; | |
[UIView setAnimationDelegate:self]; | |
[UIView setAnimationDidStopSelector:@selector(YS_barLabelHideAnimationDidStop:finished:context:)]; | |
oldLabel.alpha = 0.0f; | |
[UIView commitAnimations]; | |
} | |
[self addSubview:labelView]; | |
if (oldLabel != nil) { | |
labelView.alpha = 0.0f; | |
#if !__has_feature(objc_arc) | |
[UIView beginAnimations:nil context:oldLabel]; | |
#else | |
[UIView beginAnimations:nil context:(__bridge void *)(oldLabel)]; | |
#endif | |
[UIView setAnimationDuration:0.25]; | |
[UIView setAnimationDelay:0.25]; | |
labelView.alpha = 1.0; | |
[UIView commitAnimations]; | |
} else { | |
labelView.alpha = 1.0; | |
} | |
} | |
- (void)YS_barLabelHideAnimationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(UIView *)oldLabel { | |
[oldLabel removeFromSuperview]; | |
} | |
// Hacknicity added | |
- (void)removeLabelForComponent:(NSInteger)component | |
{ | |
[[self viewWithTag:kMagicTag + component] removeFromSuperview]; | |
} | |
- (void)removeAllLabels | |
{ | |
NSInteger n = [self numberOfComponents]; | |
for (int c = 0; c < n; c++) | |
{ | |
[self removeLabelForComponent:c]; | |
} | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment