Skip to content

Commit 5182cb2

Browse files
committed
feat: numberOfLines prop on iOS
Add multiline support for iOS picker. `numberOfLines` prop on iOS accepts integer value greater or equal to 1. Picker rows' height is based on that prop. **NOTE** in order to prevent multiline text being cut on leading and trailing edges on iOS 14, there is hardcoded horizontal padding of 20pts added to UILabel (Closes #212), potentially in future, it can be adjusted via `style` or `itemStyle` props
1 parent f543d4b commit 5182cb2

File tree

10 files changed

+101
-16
lines changed

10 files changed

+101
-16
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,12 +284,12 @@ Style to apply to each of the item labels.
284284

285285
### `numberOfLines`
286286

287-
On Android, used to truncate the text with an ellipsis after computing the text layout, including line wrapping,
287+
On Android & iOS, used to truncate the text with an ellipsis after computing the text layout, including line wrapping,
288288
such that the total number of lines does not exceed this number. Default is '1'
289289

290290
| Type | Required | Platform |
291291
| ------- | -------- | -------- |
292-
| number | No | Android |
292+
| number | No | Android, iOS |
293293

294294
### `onBlur`
295295

ios/RNCPicker.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
#import <React/UIView+React.h>
1111

12+
#import "RNCPickerLabel.h"
13+
1214
@interface RNCPicker : UIPickerView
1315

1416
@property (nonatomic, copy) NSArray<NSDictionary *> *items;
@@ -18,6 +20,8 @@
1820
@property (nonatomic, strong) UIFont *font;
1921
@property (nonatomic, assign) NSTextAlignment textAlign;
2022

23+
@property (nonatomic, assign) NSInteger numberOfLines;
24+
2125
@property (nonatomic, copy) RCTBubblingEventBlock onChange;
2226

2327
@end

ios/RNCPicker.m

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ - (instancetype)initWithFrame:(CGRect)frame
2222
_font = [UIFont systemFontOfSize:21]; // TODO: selected title default should be 23.5
2323
_selectedIndex = NSNotFound;
2424
_textAlign = NSTextAlignmentCenter;
25+
_numberOfLines = 1;
2526
self.delegate = self;
2627
self.dataSource = self;
2728
[self selectRow:0 inComponent:0 animated:YES]; // Workaround for missing selection indicator lines (see https://stackoverflow.com/questions/39564660/uipickerview-selection-indicator-not-visible-in-ios10)
@@ -48,6 +49,20 @@ - (void)setSelectedIndex:(NSInteger)selectedIndex
4849
}
4950
}
5051

52+
- (void)setNumberOfLines:(NSInteger)numberOfLines
53+
{
54+
_numberOfLines = numberOfLines;
55+
[self reloadAllComponents];
56+
[self setNeedsLayout];
57+
}
58+
59+
- (void) setFont:(UIFont *)font
60+
{
61+
_font = font;
62+
[self reloadAllComponents];
63+
[self setNeedsLayout];
64+
}
65+
5166
#pragma mark - UIPickerViewDataSource protocol
5267

5368
- (NSInteger)numberOfComponentsInPickerView:(__unused UIPickerView *)pickerView
@@ -70,33 +85,44 @@ - (NSString *)pickerView:(__unused UIPickerView *)pickerView
7085
return [RCTConvert NSString:_items[row][@"label"]];
7186
}
7287

73-
- (CGFloat)pickerView:(__unused UIPickerView *)pickerView rowHeightForComponent:(NSInteger)__unused component {
74-
return _font.pointSize + 19;
88+
- (CGFloat)pickerView:(__unused UIPickerView *)pickerView rowHeightForComponent:(__unused NSInteger) component {
89+
return (_font.lineHeight) * _numberOfLines + 20;
7590
}
7691

7792
- (UIView *)pickerView:(UIPickerView *)pickerView
7893
viewForRow:(NSInteger)row
7994
forComponent:(NSInteger)component
80-
reusingView:(UILabel *)label
95+
reusingView:(UIView *)view
8196
{
82-
if (!label) {
83-
label = [[UILabel alloc] initWithFrame:(CGRect){
84-
CGPointZero,
85-
{
86-
[pickerView rowSizeForComponent:component].width,
87-
[pickerView rowSizeForComponent:component].height,
88-
}
89-
}];
97+
if (!view) {
98+
CGFloat rowHeight = [pickerView rowSizeForComponent:component].height;
99+
CGFloat rowWidth = [pickerView rowSizeForComponent:component].width;
100+
view = [[UIView alloc] initWithFrame:CGRectZero];
101+
RNCPickerLabel* label = [[RNCPickerLabel alloc] initWithFrame:(CGRect) {
102+
CGPointZero,
103+
{
104+
rowWidth,
105+
rowHeight,
106+
}
107+
}];
108+
[view insertSubview:label atIndex:0];
90109
}
91110

111+
RNCPickerLabel* label = view.subviews[0];
92112
label.font = _font;
93113

94114
label.textColor = [RCTConvert UIColor:_items[row][@"textColor"]] ?: _color;
95115

96116
label.textAlignment = _textAlign;
97117
label.text = [self pickerView:pickerView titleForRow:row forComponent:component];
98118
label.accessibilityIdentifier = _items[row][@"testID"];
99-
return label;
119+
120+
label.numberOfLines = _numberOfLines;
121+
122+
label.leftInset = 20.0;
123+
label.rightInset = 20.0;
124+
125+
return view;
100126
}
101127

102128
- (void)pickerView:(__unused UIPickerView *)pickerView

ios/RNCPicker.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
/* Begin PBXBuildFile section */
1010
65A65136236317E000467FDE /* RNCPickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 65A65135236317E000467FDE /* RNCPickerManager.m */; };
11+
83CE2A3626889B7700470183 /* RNCPickerLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CE2A3526889B7700470183 /* RNCPickerLabel.m */; };
1112
B3E7B58A1CC2AC0600A0062D /* RNCPicker.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RNCPicker.m */; };
1213
/* End PBXBuildFile section */
1314

@@ -27,6 +28,8 @@
2728
134814201AA4EA6300B7C361 /* libRNCPicker.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNCPicker.a; sourceTree = BUILT_PRODUCTS_DIR; };
2829
65A65134236317E000467FDE /* RNCPickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNCPickerManager.h; sourceTree = "<group>"; };
2930
65A65135236317E000467FDE /* RNCPickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNCPickerManager.m; sourceTree = "<group>"; };
31+
83CE2A3426889B7700470183 /* RNCPickerLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNCPickerLabel.h; sourceTree = "<group>"; };
32+
83CE2A3526889B7700470183 /* RNCPickerLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNCPickerLabel.m; sourceTree = "<group>"; };
3033
B3E7B5881CC2AC0600A0062D /* RNCPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNCPicker.h; sourceTree = "<group>"; };
3134
B3E7B5891CC2AC0600A0062D /* RNCPicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNCPicker.m; sourceTree = "<group>"; };
3235
/* End PBXFileReference section */
@@ -53,6 +56,8 @@
5356
58B511D21A9E6C8500147676 = {
5457
isa = PBXGroup;
5558
children = (
59+
83CE2A3426889B7700470183 /* RNCPickerLabel.h */,
60+
83CE2A3526889B7700470183 /* RNCPickerLabel.m */,
5661
65A65134236317E000467FDE /* RNCPickerManager.h */,
5762
65A65135236317E000467FDE /* RNCPickerManager.m */,
5863
B3E7B5881CC2AC0600A0062D /* RNCPicker.h */,
@@ -118,6 +123,7 @@
118123
isa = PBXSourcesBuildPhase;
119124
buildActionMask = 2147483647;
120125
files = (
126+
83CE2A3626889B7700470183 /* RNCPickerLabel.m in Sources */,
121127
B3E7B58A1CC2AC0600A0062D /* RNCPicker.m in Sources */,
122128
65A65136236317E000467FDE /* RNCPickerManager.m in Sources */,
123129
);

ios/RNCPickerLabel.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#import <UIKit/UIKit.h>
2+
3+
@interface RNCPickerLabel : UILabel
4+
5+
@property (nonatomic, assign) CGFloat topInset;
6+
@property (nonatomic, assign) CGFloat bottomInset;
7+
@property (nonatomic, assign) CGFloat leftInset;
8+
@property (nonatomic, assign) CGFloat rightInset;
9+
10+
@end

ios/RNCPickerLabel.m

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#import "RNCPickerLabel.h"
2+
3+
@implementation RNCPickerLabel
4+
5+
- (instancetype)initWithFrame:(CGRect)frame
6+
{
7+
self = [super initWithFrame:frame];
8+
if (self) {
9+
self.topInset = 0.0;
10+
self.bottomInset = 0.0;
11+
self.leftInset = 0.0;
12+
self.rightInset = 0.0;
13+
}
14+
return self;
15+
}
16+
17+
- (void)drawTextInRect:(CGRect)rect
18+
{
19+
UIEdgeInsets insets = UIEdgeInsetsMake(self.topInset, self.leftInset, self.bottomInset, self.rightInset);
20+
[super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
21+
}
22+
23+
- (CGSize)intrinsicContentSize
24+
{
25+
CGSize intrinsicSuperViewContentSize = [super intrinsicContentSize];
26+
intrinsicSuperViewContentSize.height += self.topInset + self.bottomInset;
27+
intrinsicSuperViewContentSize.width += self.leftInset + self.rightInset;
28+
return intrinsicSuperViewContentSize;
29+
}
30+
31+
@end

ios/RNCPickerManager.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ - (UIView *)view
2525
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
2626
RCT_EXPORT_VIEW_PROPERTY(color, UIColor)
2727
RCT_EXPORT_VIEW_PROPERTY(textAlign, NSTextAlignment)
28+
RCT_EXPORT_VIEW_PROPERTY(numberOfLines, NSInteger)
2829
RCT_CUSTOM_VIEW_PROPERTY(fontSize, NSNumber, RNCPicker)
2930
{
3031
view.font = [RCTFont updateFont:view.font withSize:json ?: @(defaultView.font.pointSize)];

js/PickerIOS.ios.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type Props = $ReadOnly<{|
5757
onChange?: ?(event: PickerIOSChangeEvent) => mixed,
5858
onValueChange?: ?(itemValue: string | number, itemIndex: number) => mixed,
5959
selectedValue: ?(number | string),
60+
numberOfLines: ?number,
6061
|}>;
6162

6263
type State = {|
@@ -103,6 +104,10 @@ class PickerIOS extends React.Component<Props, State> {
103104
}
104105

105106
render(): React.Node {
107+
let numberOfLines = Math.round(this.props.numberOfLines ?? 1);
108+
if (numberOfLines < 1) {
109+
numberOfLines = 1;
110+
}
106111
return (
107112
<View style={this.props.style}>
108113
<RNCPickerNativeComponent
@@ -114,6 +119,7 @@ class PickerIOS extends React.Component<Props, State> {
114119
items={this.state.items}
115120
selectedIndex={this.state.selectedIndex}
116121
onChange={this._onChange}
122+
numberOfLines={numberOfLines}
117123
/>
118124
</View>
119125
);

typings/Picker.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ export interface PickerProps<T = ItemValue> extends ViewProps {
7676
*/
7777
dropdownIconColor?: string;
7878
/**
79-
* On Android, used to truncate the text with an ellipsis after computing the text layout, including line wrapping,
79+
* On Android & iOS, used to truncate the text with an ellipsis after computing the text layout, including line wrapping,
8080
* such that the total number of lines does not exceed this number. Default is '1'
81-
* @platform android
81+
* @platform android & iOS
8282
*/
8383
numberOfLines?: number;
8484
/**

typings/PickerIOS.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface PickerIOSProps extends ViewProps {
1818
onValueChange?: (itemValue: ItemValue, itemIndex: number) => void;
1919
selectedValue?: ItemValue;
2020
testID?: string;
21+
numberOfLines?: number;
2122
}
2223

2324
declare class PickerIOS extends React.Component<PickerIOSProps, {}> {

0 commit comments

Comments
 (0)