Skip to content

Commit c3bf972

Browse files
authored
fix(datetimepicker): prevent iOS 27 picker popover clipping (#684)
1 parent a671a05 commit c3bf972

1 file changed

Lines changed: 27 additions & 9 deletions

File tree

‎packages/datetimepicker/index.ios.ts‎

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,9 @@ export class DateTimePicker extends DateTimePickerBase {
9494
const alertTitle = options.title ? options.title : '';
9595
const alertController = UIAlertController.alertControllerWithTitleMessagePreferredStyle(alertTitle, DateTimePicker.PICKER_DEFAULT_MESSAGE, UIAlertControllerStyle.ActionSheet);
9696

97-
let pickerContainerFrameTop = options.title ? DateTimePicker.PICKER_DEFAULT_TITLE_OFFSET : DateTimePicker.PICKER_DEFAULT_OFFSET;
98-
if (options.title) {
99-
pickerContainerFrameTop += DateTimePicker.PICKER_DEFAULT_TITLE_HEIGHT;
100-
}
101-
const pickerViewHeight = DateTimePicker.PICKER_DEFAULT_MESSAGE_HEIGHT;
97+
const intrinsicPickerHeight = nativePicker.intrinsicContentSize.height;
98+
const pickerViewHeight = intrinsicPickerHeight > 0 ? intrinsicPickerHeight : DateTimePicker.PICKER_DEFAULT_MESSAGE_HEIGHT;
99+
102100
const pickerContainer = UIView.alloc().init();
103101
pickerContainer.clipsToBounds = false;
104102
let spinnersBackgroundColor = new Color('transparent');
@@ -116,15 +114,16 @@ export class DateTimePicker extends DateTimePickerBase {
116114

117115
const messageLabel = DateTimePicker._findLabelWithText(alertController.view, DateTimePicker.PICKER_DEFAULT_MESSAGE);
118116
const messageLabelContainer = DateTimePicker._getLabelContainer(messageLabel);
117+
messageLabel.heightAnchor.constraintEqualToConstant(pickerViewHeight).active = true;
119118
// Disable clipping on all ancestor views to prevent wheel picker from being cut off
120119
DateTimePicker._disableClipsToBoundsOnAncestors(messageLabelContainer);
121120
messageLabelContainer.addSubview(pickerContainer);
122121

123122
pickerContainer.translatesAutoresizingMaskIntoConstraints = false;
124-
pickerContainer.topAnchor.constraintEqualToAnchorConstant(alertController.view.topAnchor, pickerContainerFrameTop).active = true;
123+
pickerContainer.topAnchor.constraintEqualToAnchor(options.title ? messageLabel.topAnchor : messageLabelContainer.topAnchor).active = true;
125124
pickerContainer.leftAnchor.constraintEqualToAnchor(alertController.view.leftAnchor).active = true;
126125
pickerContainer.rightAnchor.constraintEqualToAnchor(alertController.view.rightAnchor).active = true;
127-
pickerContainer.bottomAnchor.constraintEqualToAnchor(alertController.view.bottomAnchor).active = true;
126+
pickerContainer.heightAnchor.constraintEqualToConstant(pickerViewHeight).active = true;
128127

129128
// Use auto layout for the picker - constrain to container edges to prevent clipping
130129
pickerView.translatesAutoresizingMaskIntoConstraints = false;
@@ -167,10 +166,29 @@ export class DateTimePicker extends DateTimePickerBase {
167166
}
168167

169168
if (viewController) {
169+
const iosVersion = parseFloat(Device.osVersion);
170+
const usesDefaultPhonePlacement = iosVersion >= 26 && !DateTimePicker._isTablet && (!options || options.iosPermittedArrowDirections === undefined);
171+
const needsTitleOffset = usesDefaultPhonePlacement && iosVersion < 27 && !!options.title;
172+
const positionAboveBottom = (height: number) => {
173+
const view = viewController.view;
174+
const bottom = view.bounds.origin.y + view.bounds.size.height - view.safeAreaInsets.bottom - DateTimePicker.PICKER_DEFAULT_OFFSET;
175+
const top = view.bounds.origin.y + view.safeAreaInsets.top;
176+
nativeDialog.popoverPresentationController.sourceRect = CGRectMake(view.bounds.origin.x + view.bounds.size.width / 2.0, Math.max(top, bottom - height - 1), 1.0, 1.0);
177+
};
170178
if (nativeDialog.popoverPresentationController) {
179+
const defaultArrowDirections = usesDefaultPhonePlacement ? UIPopoverArrowDirection.Up : UIPopoverArrowDirection.Any;
180+
const sourceRectY = viewController.view.bounds.size.height / 2.0;
171181
nativeDialog.popoverPresentationController.sourceView = viewController.view;
172-
nativeDialog.popoverPresentationController.sourceRect = CGRectMake(viewController.view.bounds.size.width / 2.0, viewController.view.bounds.size.height / 2.0, 1.0, 1.0);
173-
nativeDialog.popoverPresentationController.permittedArrowDirections = options && options.iosPermittedArrowDirections !== undefined ? options.iosPermittedArrowDirections : UIPopoverArrowDirection.Any;
182+
nativeDialog.popoverPresentationController.sourceRect = CGRectMake(viewController.view.bounds.size.width / 2.0, sourceRectY, 1.0, 1.0);
183+
nativeDialog.popoverPresentationController.permittedArrowDirections = options && options.iosPermittedArrowDirections !== undefined ? options.iosPermittedArrowDirections : defaultArrowDirections;
184+
nativeDialog.popoverPresentationController.canOverlapSourceViewRect = true;
185+
if (usesDefaultPhonePlacement) {
186+
// UIKit positions the popover from this estimate before presentation.
187+
const size = nativeDialog.view.systemLayoutSizeFittingSize(CGSizeMake(viewController.view.bounds.size.width, 0));
188+
const titleLabel = needsTitleOffset ? DateTimePicker._findLabelWithText(nativeDialog.view, options.title) : null;
189+
const titleHeight = titleLabel && titleLabel.intrinsicContentSize.height > 0 ? titleLabel.intrinsicContentSize.height : 0;
190+
positionAboveBottom(Math.max(0, size.height - titleHeight));
191+
}
174192
}
175193

176194
viewController.presentViewControllerAnimatedCompletion(nativeDialog, true, () => {});

0 commit comments

Comments
 (0)