Skip to content

Commit ac39795

Browse files
stigifacebook-github-bot
authored andcommitted
Fixing ActionSheetIOS position after rotation on tablet (#22738)
Summary: There's been a bug on iOS and iPad that the position of an action sheet using UIActionController isn't updated if the position of its anchor view changes due to rotating the device. A common scenario would be, presenting an action sheet from a right bar button item. Rotating the device will most likely change the bar button's X coordinate. The action sheets arrow would still point to the old position due to how it has been implemented so far. I used also reduced some code duplication between `-showActionSheetWithOptions` and `-showShareActionSheetWithOptions:` while at it. Changelog: ---------- [iOS] [Fixed] - Action Sheet position after rotation on tablet Pull Request resolved: #22738 Differential Revision: D13582810 Pulled By: PeteTheHeat fbshipit-source-id: a93065284b02efc41ae7378465521330a828a126
1 parent 7de2f77 commit ac39795

File tree

2 files changed

+144
-32
lines changed

2 files changed

+144
-32
lines changed

Diff for: Libraries/ActionSheetIOS/RCTActionSheetManager.m

+15-30
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,21 @@ - (dispatch_queue_t)methodQueue
3232
return dispatch_get_main_queue();
3333
}
3434

35-
/*
36-
* The `anchor` option takes a view to set as the anchor for the share
37-
* popup to point to, on iPads running iOS 8. If it is not passed, it
38-
* defaults to centering the share popup on screen without any arrows.
39-
*/
40-
- (CGRect)sourceRectInView:(UIView *)sourceView
41-
anchorViewTag:(NSNumber *)anchorViewTag
35+
- (void)presentViewController:(UIViewController *)alertController
36+
onParentViewController:(UIViewController *)parentViewController
37+
anchorViewTag:(NSNumber *)anchorViewTag
4238
{
39+
alertController.modalPresentationStyle = UIModalPresentationPopover;
40+
UIView *sourceView = parentViewController.view;
41+
4342
if (anchorViewTag) {
44-
UIView *anchorView = [self.bridge.uiManager viewForReactTag:anchorViewTag];
45-
return [anchorView convertRect:anchorView.bounds toView:sourceView];
43+
sourceView = [self.bridge.uiManager viewForReactTag:anchorViewTag];
4644
} else {
47-
return (CGRect){sourceView.center, {1, 1}};
45+
alertController.popoverPresentationController.permittedArrowDirections = 0;
4846
}
47+
alertController.popoverPresentationController.sourceView = sourceView;
48+
alertController.popoverPresentationController.sourceRect = sourceView.bounds;
49+
[parentViewController presentViewController:alertController animated:YES completion:nil];
4950
}
5051

5152
RCT_EXPORT_METHOD(showActionSheetWithOptions:(NSDictionary *)options
@@ -79,9 +80,7 @@ - (CGRect)sourceRectInView:(UIView *)sourceView
7980
* defaults to centering the share popup on screen without any arrows.
8081
*/
8182
NSNumber *anchorViewTag = [RCTConvert NSNumber:options[@"anchor"]];
82-
UIView *sourceView = controller.view;
83-
CGRect sourceRect = [self sourceRectInView:sourceView anchorViewTag:anchorViewTag];
84-
83+
8584
UIAlertController *alertController =
8685
[UIAlertController alertControllerWithTitle:title
8786
message:message
@@ -106,15 +105,8 @@ - (CGRect)sourceRectInView:(UIView *)sourceView
106105
index++;
107106
}
108107

109-
alertController.modalPresentationStyle = UIModalPresentationPopover;
110-
alertController.popoverPresentationController.sourceView = sourceView;
111-
alertController.popoverPresentationController.sourceRect = sourceRect;
112-
if (!anchorViewTag) {
113-
alertController.popoverPresentationController.permittedArrowDirections = 0;
114-
}
115-
[controller presentViewController:alertController animated:YES completion:nil];
116-
117108
alertController.view.tintColor = [RCTConvert UIColor:options[@"tintColor"]];
109+
[self presentViewController:alertController onParentViewController:controller anchorViewTag:anchorViewTag];
118110
}
119111

120112
RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
@@ -173,17 +165,10 @@ - (CGRect)sourceRectInView:(UIView *)sourceView
173165
}
174166
};
175167

176-
shareController.modalPresentationStyle = UIModalPresentationPopover;
177168
NSNumber *anchorViewTag = [RCTConvert NSNumber:options[@"anchor"]];
178-
if (!anchorViewTag) {
179-
shareController.popoverPresentationController.permittedArrowDirections = 0;
180-
}
181-
shareController.popoverPresentationController.sourceView = controller.view;
182-
shareController.popoverPresentationController.sourceRect = [self sourceRectInView:controller.view anchorViewTag:anchorViewTag];
183-
184-
[controller presentViewController:shareController animated:YES completion:nil];
185-
186169
shareController.view.tintColor = [RCTConvert UIColor:options[@"tintColor"]];
170+
171+
[self presentViewController:shareController onParentViewController:controller anchorViewTag:anchorViewTag];
187172
}
188173

189174
@end

Diff for: RNTester/js/ActionSheetIOSExample.js

+129-2
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ class ActionSheetExample extends React.Component<Props, State> {
5757
};
5858
}
5959

60-
class ActionSheetTintExample extends React.Component<{}, $FlowFixMeState> {
60+
class ActionSheetTintExample extends React.Component<
61+
$FlowFixMeProps,
62+
$FlowFixMeState,
63+
> {
6164
state = {
6265
clicked: 'none',
6366
};
@@ -88,6 +91,52 @@ class ActionSheetTintExample extends React.Component<{}, $FlowFixMeState> {
8891
};
8992
}
9093

94+
class ActionSheetAnchorExample extends React.Component<
95+
$FlowFixMeProps,
96+
$FlowFixMeState,
97+
> {
98+
state = {
99+
clicked: 'none',
100+
};
101+
102+
anchorRef = React.createRef();
103+
104+
render() {
105+
return (
106+
<View>
107+
<View style={style.anchorRow}>
108+
<Text style={style.button}>
109+
Click there to show the ActionSheet ->
110+
</Text>
111+
<Text
112+
onPress={this.showActionSheet}
113+
style={style.button}
114+
ref={this.anchorRef}>
115+
HERE
116+
</Text>
117+
</View>
118+
<Text>Clicked button: {this.state.clicked}</Text>
119+
</View>
120+
);
121+
}
122+
123+
showActionSheet = () => {
124+
ActionSheetIOS.showActionSheetWithOptions(
125+
{
126+
options: BUTTONS,
127+
cancelButtonIndex: CANCEL_INDEX,
128+
destructiveButtonIndex: DESTRUCTIVE_INDEX,
129+
anchor: this.anchorRef.current
130+
? ReactNative.findNodeHandle(this.anchorRef.current)
131+
: undefined,
132+
},
133+
buttonIndex => {
134+
this.setState({clicked: BUTTONS[buttonIndex]});
135+
},
136+
);
137+
};
138+
}
139+
91140
class ShareActionSheetExample extends React.Component<
92141
$FlowFixMeProps,
93142
$FlowFixMeState,
@@ -129,7 +178,10 @@ class ShareActionSheetExample extends React.Component<
129178
};
130179
}
131180

132-
class ShareScreenshotExample extends React.Component<{}, $FlowFixMeState> {
181+
class ShareScreenshotExample extends React.Component<
182+
$FlowFixMeProps,
183+
$FlowFixMeState,
184+
> {
133185
state = {
134186
text: '',
135187
};
@@ -171,11 +223,74 @@ class ShareScreenshotExample extends React.Component<{}, $FlowFixMeState> {
171223
};
172224
}
173225

226+
class ShareScreenshotAnchorExample extends React.Component<
227+
$FlowFixMeProps,
228+
$FlowFixMeState,
229+
> {
230+
state = {
231+
text: '',
232+
};
233+
234+
anchorRef = React.createRef();
235+
236+
render() {
237+
return (
238+
<View>
239+
<View style={style.anchorRow}>
240+
<Text style={style.button}>
241+
Click to show the Share ActionSheet ->
242+
</Text>
243+
<Text
244+
onPress={this.showShareActionSheet}
245+
style={style.button}
246+
ref={this.anchorRef}>
247+
HERE
248+
</Text>
249+
</View>
250+
<Text>{this.state.text}</Text>
251+
</View>
252+
);
253+
}
254+
255+
showShareActionSheet = () => {
256+
// Take the snapshot (returns a temp file uri)
257+
takeSnapshot('window')
258+
.then(uri => {
259+
// Share image data
260+
ActionSheetIOS.showShareActionSheetWithOptions(
261+
{
262+
url: uri,
263+
excludedActivityTypes: ['com.apple.UIKit.activity.PostToTwitter'],
264+
anchor: this.anchorRef.current
265+
? ReactNative.findNodeHandle(this.anchorRef.current)
266+
: undefined,
267+
},
268+
error => Alert.alert('Error', error),
269+
(completed, method) => {
270+
let text;
271+
if (completed) {
272+
text = `Shared via ${method}`;
273+
} else {
274+
text = "You didn't share";
275+
}
276+
this.setState({text});
277+
},
278+
);
279+
})
280+
.catch(error => Alert.alert('Error', error));
281+
};
282+
}
283+
174284
const style = StyleSheet.create({
175285
button: {
176286
marginBottom: 10,
177287
fontWeight: '500',
178288
},
289+
anchorRow: {
290+
flex: 1,
291+
flexDirection: 'row',
292+
justifyContent: 'space-between',
293+
},
179294
});
180295

181296
exports.title = 'ActionSheetIOS';
@@ -193,6 +308,12 @@ exports.examples = [
193308
return <ActionSheetTintExample />;
194309
},
195310
},
311+
{
312+
title: 'Show Action Sheet with anchor',
313+
render(): React.Element<any> {
314+
return <ActionSheetAnchorExample />;
315+
},
316+
},
196317
{
197318
title: 'Show Share Action Sheet',
198319
render(): React.Element<any> {
@@ -211,4 +332,10 @@ exports.examples = [
211332
return <ShareScreenshotExample />;
212333
},
213334
},
335+
{
336+
title: 'Share from Anchor',
337+
render(): React.Element<any> {
338+
return <ShareScreenshotAnchorExample />;
339+
},
340+
},
214341
];

0 commit comments

Comments
 (0)