From 4420e391abe4891fb776f62772eefc2dde1f4349 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Tue, 18 Sep 2018 18:23:55 -0700 Subject: [PATCH] Fix InputAccessoryView safe area when not attached to a TextInput (#21179) Summary: When using an InputAccessoryView attached to a TextInput the safe area insets are not applied properly. This uses different autolayout constraints that works in all cases I tested, roughly based on the technique used here https://github.com/stockx/SafeAreaInputAccessoryViewWrapperView/blob/master/SafeAreaInputAccessoryViewWrapperView/Classes/SafeAreaInputAccessoryViewWrapperView.swift#L38. Pull Request resolved: https://github.com/facebook/react-native/pull/21179 Differential Revision: D9928503 Pulled By: hramos fbshipit-source-id: b1b623334558093042fd94ac85e1b52dd16aa1a0 --- .../Text/TextInput/RCTInputAccessoryView.m | 2 +- .../TextInput/RCTInputAccessoryViewContent.m | 50 +++++++++++++------ RNTester/js/InputAccessoryViewExample.js | 15 ++++-- 3 files changed, 47 insertions(+), 20 deletions(-) diff --git a/Libraries/Text/TextInput/RCTInputAccessoryView.m b/Libraries/Text/TextInput/RCTInputAccessoryView.m index 1f66e473966..9ccaaa654a5 100644 --- a/Libraries/Text/TextInput/RCTInputAccessoryView.m +++ b/Libraries/Text/TextInput/RCTInputAccessoryView.m @@ -42,7 +42,7 @@ - (void)reactSetFrame:(CGRect)frame { - [_inputAccessoryView setFrame:frame]; + [_inputAccessoryView reactSetFrame:frame]; if (_shouldBecomeFirstResponder) { _shouldBecomeFirstResponder = NO; diff --git a/Libraries/Text/TextInput/RCTInputAccessoryViewContent.m b/Libraries/Text/TextInput/RCTInputAccessoryViewContent.m index 5a9f142c610..3d62f4922aa 100644 --- a/Libraries/Text/TextInput/RCTInputAccessoryViewContent.m +++ b/Libraries/Text/TextInput/RCTInputAccessoryViewContent.m @@ -12,6 +12,7 @@ @implementation RCTInputAccessoryViewContent { UIView *_safeAreaContainer; + NSLayoutConstraint *_heightConstraint; } - (instancetype)init @@ -19,31 +20,48 @@ if (self = [super init]) { _safeAreaContainer = [UIView new]; [self addSubview:_safeAreaContainer]; + + // Use autolayout to position the view properly and take into account + // safe area insets on iPhone X. + // TODO: Support rotation, anchor to left and right without breaking frame x coordinate (T27974328). + self.autoresizingMask = UIViewAutoresizingFlexibleHeight; + _safeAreaContainer.translatesAutoresizingMaskIntoConstraints = NO; + + _heightConstraint = [_safeAreaContainer.heightAnchor constraintEqualToConstant:0]; + _heightConstraint.active = YES; + + if (@available(iOS 11.0, *)) { + [_safeAreaContainer.bottomAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.bottomAnchor].active = YES; + [_safeAreaContainer.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor].active = YES; + [_safeAreaContainer.leadingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leadingAnchor].active = YES; + [_safeAreaContainer.trailingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.trailingAnchor].active = YES; + } else { + [_safeAreaContainer.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES; + [_safeAreaContainer.topAnchor constraintEqualToAnchor:self.topAnchor].active = YES; + [_safeAreaContainer.leadingAnchor constraintEqualToAnchor:self.leadingAnchor].active = YES; + [_safeAreaContainer.trailingAnchor constraintEqualToAnchor:self.trailingAnchor].active = YES; + } } return self; } -- (void)didMoveToSuperview +- (CGSize)intrinsicContentSize { - -#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ - // Avoid the home pill (in portrait mode) - // TODO: Support rotation, anchor to left and right without breaking frame x coordinate (T27974328). - if (@available(iOS 11.0, *)) { - if (self.window) { - [_safeAreaContainer.bottomAnchor - constraintLessThanOrEqualToSystemSpacingBelowAnchor:self.window.safeAreaLayoutGuide.bottomAnchor - multiplier:1.0f].active = YES; - } - } -#endif - + // This is needed so the view size is based on autolayout constraints. + return CGSizeZero; } -- (void)setFrame:(CGRect)frame +- (void)reactSetFrame:(CGRect)frame { - [super setFrame:frame]; + // We still need to set the frame here, otherwise it won't be + // measured until moved to the window during the keyboard opening + // animation. If this happens, the height will be animated from 0 to + // its actual size and we don't want that. + [self setFrame:frame]; [_safeAreaContainer setFrame:frame]; + + _heightConstraint.constant = frame.size.height; + [self layoutIfNeeded]; } - (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)index diff --git a/RNTester/js/InputAccessoryViewExample.js b/RNTester/js/InputAccessoryViewExample.js index a848b961436..33d53092575 100644 --- a/RNTester/js/InputAccessoryViewExample.js +++ b/RNTester/js/InputAccessoryViewExample.js @@ -58,6 +58,8 @@ class TextInputBar extends React.PureComponent<*, *> { } } +const BAR_HEIGHT = 44; + class InputAccessoryViewExample extends React.Component<*> { static title = ''; static description = @@ -65,8 +67,8 @@ class InputAccessoryViewExample extends React.Component<*> { render() { return ( - - + <> + {Array(15) .fill() .map((_, i) => )} @@ -74,14 +76,21 @@ class InputAccessoryViewExample extends React.Component<*> { - + ); } } const styles = StyleSheet.create({ + fill: { + flex: 1, + }, textInputContainer: { flexDirection: 'row', + alignItems: 'center', + borderTopWidth: 1, + borderTopColor: '#eee', + height: BAR_HEIGHT, }, textInput: { flex: 1,