Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion example/src/PickerIOSExample.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import {Text, StyleSheet, View} from 'react-native';
import {Button, Text, StyleSheet, View} from 'react-native';
import {PickerIOS} from '@react-native-picker/picker';

const CAR_MAKES_AND_MODELS = {
Expand Down Expand Up @@ -127,6 +127,32 @@ function PickerExample() {
);
}

function ControlledPickerRapidUpdatesExample() {
const [value, setValue] = React.useState('first');

return (
<View>
<Text>Rapidly alternate the two buttons while the wheel is moving.</Text>
<Button title="Select first" onPress={() => setValue('first')} />
<Button title="Select last" onPress={() => setValue('last')} />
<PickerIOS
accessibilityLabel="Controlled picker rapid updates reproduction"
selectedValue={value}
testID="controlled-picker-rapid-updates"
onValueChange={(nextValue) => setValue(nextValue)}
>
<PickerIOS.Item label="First" value="first" />
<PickerIOS.Item label="Second" value="second" />
<PickerIOS.Item label="Third" value="third" />
<PickerIOS.Item label="Last" value="last" />
</PickerIOS>
<Text testID="controlled-picker-rapid-updates-value">
Selected: {value}
</Text>
</View>
);
}

function PickerStyleExample() {
const [carMake, setCarMake] = React.useState<string>('cadillac');

Expand Down Expand Up @@ -156,6 +182,10 @@ export const examples = [
title: '<PickerIOS>',
render: PickerExample,
},
{
title: '<PickerIOS> with rapid controlled updates',
render: ControlledPickerRapidUpdatesExample,
},
{
title: '<PickerIOS> with custom styling',
render: PickerStyleExample,
Expand Down
49 changes: 46 additions & 3 deletions ios/RNCPicker.mm
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#import <React/RCTUtils.h>

@interface RNCPicker() <UIPickerViewDataSource, UIPickerViewDelegate, UIPickerViewAccessibilityDelegate>
@property (nonatomic, assign) NSUInteger selectionGeneration;
@end

@implementation RNCPicker
Expand All @@ -39,8 +40,26 @@ - (instancetype)initWithFrame:(CGRect)frame

RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)

// Selection is deferred so UIPickerView can finish processing the props update.
// A native callback or reload may establish a newer selection before this runs.
- (void)scheduleSelectionOfRow:(NSInteger)row
animated:(BOOL)animated
generation:(NSUInteger)generation
{
dispatch_async(dispatch_get_main_queue(), ^{
if (self.selectionGeneration == generation) {
[self selectRow:row inComponent:0 animated:animated];
}
});
}

- (void)setItems:(NSArray<NSDictionary *> *)items
{
if ([_items isEqualToArray:items]) {
return;
}

_selectionGeneration += 1;
_items = [items copy];
[self setNeedsLayout];
}
Expand All @@ -50,21 +69,43 @@ - (void)setSelectedIndex:(NSInteger)selectedIndex
if (_selectedIndex != selectedIndex) {
BOOL animated = _selectedIndex != NSNotFound; // Don't animate the initial value
_selectedIndex = selectedIndex;
dispatch_async(dispatch_get_main_queue(), ^{
[self selectRow:selectedIndex inComponent:0 animated:animated];
});
NSUInteger selectionGeneration = ++_selectionGeneration;
[self scheduleSelectionOfRow:selectedIndex
animated:animated
generation:selectionGeneration];
}
}

- (void)reloadAllComponents
{
// Re-queue the latest known selection after the reload without animation.
NSUInteger selectionGeneration = ++_selectionGeneration;
[super reloadAllComponents];

if (_selectedIndex != NSNotFound && _selectedIndex < (NSInteger)_items.count) {
[self scheduleSelectionOfRow:_selectedIndex
animated:NO
generation:selectionGeneration];
}
}

- (void)setNumberOfLines:(NSInteger)numberOfLines
{
if (_numberOfLines == numberOfLines) {
return;
}

_numberOfLines = numberOfLines;
[self reloadAllComponents];
[self setNeedsLayout];
}

- (void) setFont:(UIFont *)font
{
if ([_font isEqual:font]) {
return;
}

_font = font;
[self reloadAllComponents];
[self setNeedsLayout];
Expand Down Expand Up @@ -135,6 +176,7 @@ - (UIView *)pickerView:(UIPickerView *)pickerView
- (void)pickerView:(__unused UIPickerView *)pickerView
didSelectRow:(NSInteger)row inComponent:(__unused NSInteger)component
{
_selectionGeneration += 1;
_selectedIndex = row;
if (_onChange && _items.count > (NSUInteger)row) {
_onChange(@{
Expand Down Expand Up @@ -162,6 +204,7 @@ - (void)pickerView:(__unused UIPickerView *)pickerView
didSelectRow:(NSInteger)row inComponent:(__unused NSInteger)component
withEventEmitter:(facebook::react::SharedViewEventEmitter)eventEmitter
{
_selectionGeneration += 1;
_selectedIndex = row;
if (eventEmitter != nullptr && _items.count > (NSUInteger)row) {
std::dynamic_pointer_cast<const facebook::react::RNCPickerEventEmitter>(eventEmitter)
Expand Down