Skip to content

Commit a34f667

Browse files
fortmarekvonovak
andauthored
fix: compute date picker dimensions in native (#656)
* Compute date picker width * Showcase width with Text * fix: use UIDatePickerStyle value for dimension measurements * refactor: remove unnecessary code for height detection * Remove no longer relevant test * Remove unused mock Co-authored-by: Vojtech Novak <vonovak@gmail.com>
1 parent 92f47ed commit a34f667

File tree

9 files changed

+120
-185
lines changed

9 files changed

+120
-185
lines changed

example/App.js

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -319,25 +319,31 @@ export const App = () => {
319319
title="toggleMinMaxDate"
320320
/>
321321
</View>
322-
{show && (
323-
<DateTimePicker
324-
testID="dateTimePicker"
325-
timeZoneOffsetInMinutes={tzOffsetInMinutes}
326-
minuteInterval={interval}
327-
maximumDate={maximumDate}
328-
minimumDate={minimumDate}
329-
value={date}
330-
mode={mode}
331-
is24Hour
332-
display={display}
333-
onChange={onChange}
334-
style={styles.iOsPicker}
335-
textColor={textColor || undefined}
336-
accentColor={accentColor || undefined}
337-
neutralButtonLabel={neutralButtonLabel}
338-
disabled={disabled}
339-
/>
340-
)}
322+
<View style={{flexDirection: 'row', alignItems: 'center'}}>
323+
{/* This label ensures there is no regression in this former bug: https://github.com/react-native-datetimepicker/datetimepicker/issues/409 */}
324+
<Text style={{flexShrink: 1}}>
325+
This is a very very very very very very long text to showcase
326+
behavior
327+
</Text>
328+
{show && (
329+
<DateTimePicker
330+
testID="dateTimePicker"
331+
timeZoneOffsetInMinutes={tzOffsetInMinutes}
332+
minuteInterval={interval}
333+
maximumDate={maximumDate}
334+
minimumDate={minimumDate}
335+
value={date}
336+
mode={mode}
337+
is24Hour
338+
display={display}
339+
onChange={onChange}
340+
textColor={textColor || undefined}
341+
accentColor={accentColor || undefined}
342+
neutralButtonLabel={neutralButtonLabel}
343+
disabled={disabled}
344+
/>
345+
)}
346+
</View>
341347
</View>
342348
</ScrollView>
343349
</SafeAreaView>
@@ -552,9 +558,6 @@ const styles = StyleSheet.create({
552558
fontSize: 16,
553559
fontWeight: 'normal',
554560
},
555-
iOsPicker: {
556-
flex: 1,
557-
},
558561
windowsPicker: {
559562
flex: 1,
560563
paddingTop: 10,

ios/RNDateTimePickerManager.m

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
#import "RNDateTimePickerManager.h"
9+
#import "RNDateTimePickerShadowView.h"
910

1011
#import <React/RCTBridge.h>
1112
#import <React/RCTEventDispatcher.h>
@@ -53,15 +54,35 @@ @implementation RCTConvert(UIDatePicker)
5354

5455
@end
5556

56-
@implementation RNDateTimePickerManager
57+
@implementation RNDateTimePickerManager {
58+
RNDateTimePicker* _picker;
59+
}
5760

5861
RCT_EXPORT_MODULE()
5962

63+
- (instancetype)init {
64+
if (self = [super init]) {
65+
_picker = [RNDateTimePicker new];
66+
}
67+
return self;
68+
}
69+
70+
+ (BOOL)requiresMainQueueSetup {
71+
return true;
72+
}
73+
6074
- (UIView *)view
6175
{
6276
return [RNDateTimePicker new];
6377
}
6478

79+
- (RCTShadowView *)shadowView
80+
{
81+
RNDateTimePickerShadowView* shadowView = [RNDateTimePickerShadowView new];
82+
shadowView.picker = _picker;
83+
return shadowView;
84+
}
85+
6586
+ (NSString*) datepickerStyleToString: (UIDatePickerStyle) style API_AVAILABLE(ios(13.4)){
6687
// RCTConvert does not handle this.?
6788
switch (style) {
@@ -79,28 +100,10 @@ + (NSString*) datepickerStyleToString: (UIDatePickerStyle) style API_AVAILABLE(
79100
}
80101
}
81102

82-
RCT_EXPORT_METHOD(getDefaultDisplayValue:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
83-
{
84-
dispatch_async(dispatch_get_main_queue(), ^{
85-
if (@available(iOS 13.4, *)) {
86-
UIDatePicker* view = [RNDateTimePicker new];
87-
88-
view.preferredDatePickerStyle = UIDatePickerStyleAutomatic;
89-
UIDatePickerMode renderedMode = [RCTConvert UIDatePickerMode:options[@"mode"]];
90-
view.datePickerMode = renderedMode;
91-
// NOTE afaict we do not need to measure the actual dimensions here, but if we do, just look at the original PR
92-
93-
UIDatePickerStyle determinedDisplayValue = view.datePickerStyle;
94-
95-
resolve(@{
96-
@"determinedDisplayValue": [RNDateTimePickerManager datepickerStyleToString:determinedDisplayValue],
97-
});
98-
} else {
99-
// never happens; the condition is just to avoid compiler warnings
100-
reject(@"UNEXPECTED_CALL", @"unexpected getDefaultDisplayValue() call", nil);
101-
}
102-
});
103-
}
103+
RCT_EXPORT_SHADOW_PROPERTY(date, NSDate)
104+
RCT_EXPORT_SHADOW_PROPERTY(mode, UIDatePickerMode)
105+
RCT_EXPORT_SHADOW_PROPERTY(locale, NSLocale)
106+
RCT_EXPORT_SHADOW_PROPERTY(displayIOS, RNCUIDatePickerStyle)
104107

105108
RCT_EXPORT_VIEW_PROPERTY(date, NSDate)
106109
RCT_EXPORT_VIEW_PROPERTY(locale, NSLocale)

ios/RNDateTimePickerShadowView.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#import <React/RCTShadowView.h>
2+
#import "RNDateTimePicker.h"
3+
4+
@interface RNDateTimePickerShadowView : RCTShadowView
5+
6+
@property (nullable, nonatomic, strong) RNDateTimePicker *picker;
7+
@property (nonatomic) UIDatePickerMode mode;
8+
@property (nullable, nonatomic, strong) NSDate *date;
9+
@property (nullable, nonatomic, strong) NSLocale *locale;
10+
@property (nonatomic, assign) UIDatePickerStyle displayIOS API_AVAILABLE(ios(13.4));
11+
12+
@end

ios/RNDateTimePickerShadowView.m

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#import "RNDateTimePickerShadowView.h"
2+
3+
@implementation RNDateTimePickerShadowView
4+
5+
- (instancetype)init
6+
{
7+
if (self = [super init]) {
8+
YGNodeSetMeasureFunc(self.yogaNode, RNDateTimePickerShadowViewMeasure);
9+
}
10+
return self;
11+
}
12+
13+
- (void)setDate:(NSDate *)date {
14+
_date = date;
15+
YGNodeMarkDirty(self.yogaNode);
16+
}
17+
18+
- (void)setLocale:(NSLocale *)locale {
19+
_locale = locale;
20+
YGNodeMarkDirty(self.yogaNode);
21+
}
22+
23+
- (void)setMode:(UIDatePickerMode)mode {
24+
_mode = mode;
25+
YGNodeMarkDirty(self.yogaNode);
26+
}
27+
28+
29+
- (void)setDisplayIOS:(UIDatePickerStyle)displayIOS {
30+
_displayIOS = displayIOS;
31+
YGNodeMarkDirty(self.yogaNode);
32+
}
33+
34+
static YGSize RNDateTimePickerShadowViewMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode)
35+
{
36+
RNDateTimePickerShadowView *shadowPickerView = (__bridge RNDateTimePickerShadowView *)YGNodeGetContext(node);
37+
38+
__block CGSize size;
39+
dispatch_sync(dispatch_get_main_queue(), ^{
40+
[shadowPickerView.picker setDate:shadowPickerView.date];
41+
[shadowPickerView.picker setDatePickerMode:shadowPickerView.mode];
42+
[shadowPickerView.picker setLocale:shadowPickerView.locale];
43+
if (@available(iOS 14.0, *)) {
44+
[shadowPickerView.picker setPreferredDatePickerStyle:shadowPickerView.displayIOS];
45+
}
46+
size = [shadowPickerView.picker sizeThatFits:UILayoutFittingCompressedSize];
47+
});
48+
49+
return (YGSize){
50+
RCTYogaFloatFromCoreGraphicsFloat(size.width),
51+
RCTYogaFloatFromCoreGraphicsFloat(size.height)
52+
};
53+
}
54+
55+
@end

jest/setup.js

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +0,0 @@
1-
import {NativeModules} from 'react-native';
2-
NativeModules.RNDateTimePickerManager = {
3-
getDefaultDisplayValue: jest.fn(() =>
4-
Promise.resolve({
5-
determinedDisplayValue: 'spinner',
6-
}),
7-
),
8-
};

src/datetimepicker.ios.js

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ import {sharedPropsValidation, toMilliseconds} from './utils';
1414
import {IOS_DISPLAY, ANDROID_MODE, EVENT_TYPE_SET} from './constants';
1515
import invariant from 'invariant';
1616
import * as React from 'react';
17-
import {getPickerHeightStyle} from './layoutUtilsIOS';
18-
import {Platform, StyleSheet} from 'react-native';
17+
import {Platform} from 'react-native';
1918

2019
import type {
2120
NativeEventIOS,
@@ -61,7 +60,6 @@ export default function Picker({
6160
}: IOSNativeProps): React.Node {
6261
sharedPropsValidation({value});
6362

64-
const [heightStyle, setHeightStyle] = React.useState(undefined);
6563
const _picker: NativeRef = React.useRef(null);
6664
const display = getDisplaySafe(providedDisplay);
6765

@@ -80,18 +78,6 @@ export default function Picker({
8078
[onChange, value],
8179
);
8280

83-
React.useEffect(
84-
function ensureCorrectHeight() {
85-
const height = getPickerHeightStyle(display, mode);
86-
if (height instanceof Promise) {
87-
height.then(setHeightStyle);
88-
} else {
89-
setHeightStyle(height);
90-
}
91-
},
92-
[display, mode],
93-
);
94-
9581
const _onChange = (event: NativeEventIOS) => {
9682
const timestamp = event.nativeEvent.timestamp;
9783
// $FlowFixMe Cannot assign object literal to `unifiedEvent` because number [1] is incompatible with undefined [2] in property `nativeEvent.timestamp`.
@@ -104,11 +90,6 @@ export default function Picker({
10490

10591
invariant(value, 'A date or time should be specified as `value`.');
10692

107-
if (!heightStyle) {
108-
// wait for height to be available in state
109-
return null;
110-
}
111-
11293
const dates: DatePickerOptions = {value, maximumDate, minimumDate};
11394
toMilliseconds(dates, 'value', 'minimumDate', 'maximumDate');
11495

@@ -117,7 +98,7 @@ export default function Picker({
11798
<RNDateTimePicker
11899
testID={testID}
119100
ref={_picker}
120-
style={StyleSheet.compose(heightStyle, style)}
101+
style={style}
121102
date={dates.value}
122103
locale={locale !== null && locale !== '' ? locale : undefined}
123104
maximumDate={dates.maximumDate}

src/layoutUtilsIOS.js

Lines changed: 0 additions & 71 deletions
This file was deleted.

test/__snapshots__/index.test.js.snap

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,5 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`DateTimePicker RNDateTimePickerIOS style props applies styling to DatePicker 1`] = `
4-
<RNDateTimePicker
5-
date={1376949600000}
6-
displayIOS="default"
7-
enabled={true}
8-
mode="date"
9-
onChange={[Function]}
10-
onResponderTerminationRequest={[Function]}
11-
onStartShouldSetResponder={[Function]}
12-
style={
13-
[
14-
{
15-
"height": 216,
16-
},
17-
{
18-
"backgroundColor": "red",
19-
},
20-
]
21-
}
22-
/>
23-
`;
24-
253
exports[`DateTimePicker given a component for android / iOS renders a component (iOS) / null (android) 1`] = `
264
<RNDateTimePicker
275
date={1376949600000}
@@ -31,11 +9,6 @@ exports[`DateTimePicker given a component for android / iOS renders a component
319
onChange={[Function]}
3210
onResponderTerminationRequest={[Function]}
3311
onStartShouldSetResponder={[Function]}
34-
style={
35-
{
36-
"height": 216,
37-
}
38-
}
3912
/>
4013
`;
4114

0 commit comments

Comments
 (0)