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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#import "RCTDefaultReactNativeFactoryDelegate.h"
#import <ReactCommon/RCTHost.h>
#import <React/RCTViewController.h>
#import "RCTAppSetupUtils.h"
#import "RCTDependencyProvider.h"
#if USE_THIRD_PARTY_JSC != 1
Expand All @@ -28,7 +29,7 @@ - (NSURL *_Nullable)sourceURLForBridge:(nonnull RCTBridge *)bridge

- (UIViewController *)createRootViewController
{
return [UIViewController new];
return [RCTViewController new];
}

- (RCTBridge *)createBridgeWithDelegate:(id<RCTBridgeDelegate>)delegate launchOptions:(NSDictionary *)launchOptions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#import <React/RCTUITextField.h>
#import <React/RCTUITextView.h>
#import <React/RCTUtils.h>
#import <React/UIView+React.h>
#import <React/UIViewController+React.h>

#import "RCTConversions.h"
#import "RCTTextInputNativeCommands.h"
Expand All @@ -32,6 +34,7 @@

@interface RCTTextInputComponentView () <
RCTBackedTextInputDelegate,
RCTViewControllerAppearanceListener,
RCTTextInputViewProtocol
#if !TARGET_OS_TV
,
Expand Down Expand Up @@ -70,6 +73,8 @@ @implementation RCTTextInputComponentView {
*/
BOOL _comingFromJS;
BOOL _didMoveToWindow;
BOOL _didAutoFocus;
__weak UIViewController *_viewController;

/*
* Newly initialized default typing attributes contain a no-op NSParagraphStyle and NSShadow. These cause inequality
Expand All @@ -95,6 +100,7 @@ - (instancetype)initWithFrame:(CGRect)frame
_ignoreNextTextInputCall = NO;
_comingFromJS = NO;
_didMoveToWindow = NO;
_didAutoFocus = NO;
_originalTypingAttributes = [_backedTextInputView.typingAttributes copy];
_previousContentSize = CGSizeZero;

Expand All @@ -121,13 +127,18 @@ - (void)didMoveToWindow
{
[super didMoveToWindow];

bool enableNewAutoFocusImpl = ReactNativeFeatureFlags::enableIOSExperimentalAutoFocusImplementation();
if (enableNewAutoFocusImpl) {
[_viewController reactRemoveViewControllerAppearanceListener:self];
_viewController = self.window ? [self reactViewController] : nil;
[_viewController reactAddViewControllerAppearanceListener:self];
}

if (self.window && !_didMoveToWindow) {
const auto &props = static_cast<const TextInputProps &>(*_props);
if (props.autoFocus) {
[_backedTextInputView becomeFirstResponder];
[self scrollCursorIntoView];
}
_didMoveToWindow = YES;
if (!enableNewAutoFocusImpl) {
[self tryAutoFocus];
}
[self initializeReturnKeyType];
}

Expand Down Expand Up @@ -384,12 +395,32 @@ - (void)prepareForRecycle
_lastStringStateWasUpdatedWith = nil;
_ignoreNextTextInputCall = NO;
_didMoveToWindow = NO;
_didAutoFocus = NO;
[_viewController reactRemoveViewControllerAppearanceListener:self];
_viewController = nil;
_backedTextInputView.inputAccessoryViewID = nil;
_backedTextInputView.inputAccessoryView = nil;
_hasInputAccessoryView = false;
[_backedTextInputView resignFirstResponder];
}

#pragma mark - Auto focus / RCTViewControllerAppearanceListener

- (void)tryAutoFocus
{
const auto &props = static_cast<const TextInputProps &>(*_props);
if (props.autoFocus && !_didAutoFocus) {
_didAutoFocus = YES;
[_backedTextInputView becomeFirstResponder];
[self scrollCursorIntoView];
}
}

- (void)reactViewControllerDidAppear:(UIViewController *)viewController animated:(BOOL)animated
{
[self tryAutoFocus];
}

#pragma mark - RCTBackedTextInputDelegate

- (BOOL)textInputShouldBeginEditing
Expand Down
17 changes: 17 additions & 0 deletions packages/react-native/React/Views/RCTViewController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/


#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface RCTViewController : UIViewController

@end

NS_ASSUME_NONNULL_END
29 changes: 29 additions & 0 deletions packages/react-native/React/Views/RCTViewController.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// RCTViewController.m
// React-Core
//
// Created by Hanno Goedecke on 27.04.26.
//

#import "RCTViewController.h"
#import <React/UIViewController+React.h>

@interface RCTViewController ()

@end

@implementation RCTViewController

- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self reactNotifyViewControllerDidAppear:animated];
}

- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self reactNotifyViewControllerDidDisappear:animated];
}

@end
4 changes: 2 additions & 2 deletions packages/react-native/React/Views/RCTWrapperViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/

#import <UIKit/UIKit.h>
#import <React/RCTViewController.h>

@class RCTWrapperViewController;

@interface RCTWrapperViewController : UIViewController
@interface RCTWrapperViewController : RCTViewController

- (instancetype)initWithContentView:(UIView *)contentView NS_DESIGNATED_INITIALIZER;

Expand Down
36 changes: 36 additions & 0 deletions packages/react-native/React/Views/UIViewController+React.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@protocol RCTViewControllerAppearanceListener <NSObject>

@optional
- (void)reactViewControllerDidAppear:(UIViewController *)viewController animated:(BOOL)animated;
- (void)reactViewControllerDidDisappear:(UIViewController *)viewController animated:(BOOL)animated;

@end

@interface UIViewController (React)

@property (nonatomic, assign, readonly) BOOL reactViewControllerIsVisible;

- (void)reactAddViewControllerAppearanceListener:(id<RCTViewControllerAppearanceListener>)listener;
- (void)reactRemoveViewControllerAppearanceListener:(id<RCTViewControllerAppearanceListener>)listener;

/**
* Call from `viewDidAppear:` / `viewDidDisappear:` in UIViewController subclasses
* that want to notify registered React Native appearance listeners.
*/
- (void)reactNotifyViewControllerDidAppear:(BOOL)animated;
- (void)reactNotifyViewControllerDidDisappear:(BOOL)animated;

@end

NS_ASSUME_NONNULL_END
89 changes: 89 additions & 0 deletions packages/react-native/React/Views/UIViewController+React.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#import "UIViewController+React.h"

#import <objc/runtime.h>

@interface RCTViewControllerAppearanceState : NSObject

@property (nonatomic, strong, readonly) NSHashTable<id<RCTViewControllerAppearanceListener>> *listeners;
@property (nonatomic, assign) BOOL visible;

@end

@implementation RCTViewControllerAppearanceState

- (instancetype)init
{
if (self = [super init]) {
_listeners = [NSHashTable weakObjectsHashTable];
}
return self;
}

@end

@implementation UIViewController (React)

- (RCTViewControllerAppearanceState *)reactViewControllerAppearanceState
{
RCTViewControllerAppearanceState *state =
objc_getAssociatedObject(self, @selector(reactViewControllerAppearanceState));
if (!state) {
state = [RCTViewControllerAppearanceState new];
objc_setAssociatedObject(
self, @selector(reactViewControllerAppearanceState), state, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return state;
}

- (BOOL)reactViewControllerIsVisible
{
return [self reactViewControllerAppearanceState].visible;
}

- (void)reactAddViewControllerAppearanceListener:(id<RCTViewControllerAppearanceListener>)listener
{
RCTViewControllerAppearanceState *state = [self reactViewControllerAppearanceState];
[state.listeners addObject:listener];

if (state.visible && [listener respondsToSelector:@selector(reactViewControllerDidAppear:animated:)]) {
[listener reactViewControllerDidAppear:self animated:NO];
}
}

- (void)reactRemoveViewControllerAppearanceListener:(id<RCTViewControllerAppearanceListener>)listener
{
[[self reactViewControllerAppearanceState].listeners removeObject:listener];
}

- (void)reactNotifyViewControllerDidAppear:(BOOL)animated
{
RCTViewControllerAppearanceState *state = [self reactViewControllerAppearanceState];
state.visible = YES;

for (id<RCTViewControllerAppearanceListener> listener in state.listeners.allObjects) {
if ([listener respondsToSelector:@selector(reactViewControllerDidAppear:animated:)]) {
[listener reactViewControllerDidAppear:self animated:animated];
}
}
}

- (void)reactNotifyViewControllerDidDisappear:(BOOL)animated
{
RCTViewControllerAppearanceState *state = [self reactViewControllerAppearanceState];
state.visible = NO;

for (id<RCTViewControllerAppearanceListener> listener in state.listeners.allObjects) {
if ([listener respondsToSelector:@selector(reactViewControllerDidDisappear:animated:)]) {
[listener reactViewControllerDidDisappear:self animated:animated];
}
}
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<eb70fd41bc36f1a49849e29bea081007>>
* @generated SignedSource<<1e2cf81bf4354a7e3d9d09384d813cfc>>
*/

/**
Expand Down Expand Up @@ -186,6 +186,12 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun enableFontScaleChangesUpdatingLayout(): Boolean = accessor.enableFontScaleChangesUpdatingLayout()

/**
* Fixes #56595 by moving the autoFocus from didMoveToWindow to viewDidAppear
*/
@JvmStatic
public fun enableIOSExperimentalAutoFocusImplementation(): Boolean = accessor.enableIOSExperimentalAutoFocusImplementation()

/**
* Applies base offset for each line of text separately on iOS.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<f79ca61a0da053a1661eca4d3a35b081>>
* @generated SignedSource<<8dd2a1c47c059810961eb66b13b18dc1>>
*/

/**
Expand Down Expand Up @@ -46,6 +46,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var enableFabricLogsCache: Boolean? = null
private var enableFabricRendererCache: Boolean? = null
private var enableFontScaleChangesUpdatingLayoutCache: Boolean? = null
private var enableIOSExperimentalAutoFocusImplementationCache: Boolean? = null
private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null
private var enableIOSViewClipToPaddingBoxCache: Boolean? = null
private var enableImagePrefetchingAndroidCache: Boolean? = null
Expand Down Expand Up @@ -346,6 +347,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}

override fun enableIOSExperimentalAutoFocusImplementation(): Boolean {
var cached = enableIOSExperimentalAutoFocusImplementationCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.enableIOSExperimentalAutoFocusImplementation()
enableIOSExperimentalAutoFocusImplementationCache = cached
}
return cached
}

override fun enableIOSTextBaselineOffsetPerLine(): Boolean {
var cached = enableIOSTextBaselineOffsetPerLineCache
if (cached == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<62d1f0fcdf8e3165480da12575d62826>>
* @generated SignedSource<<a6ccb3bb68e8b300fdabf085e7ea6979>>
*/

/**
Expand Down Expand Up @@ -80,6 +80,8 @@ public object ReactNativeFeatureFlagsCxxInterop {

@DoNotStrip @JvmStatic public external fun enableFontScaleChangesUpdatingLayout(): Boolean

@DoNotStrip @JvmStatic public external fun enableIOSExperimentalAutoFocusImplementation(): Boolean

@DoNotStrip @JvmStatic public external fun enableIOSTextBaselineOffsetPerLine(): Boolean

@DoNotStrip @JvmStatic public external fun enableIOSViewClipToPaddingBox(): Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<84ac5b80585f9185c879c81822718d86>>
* @generated SignedSource<<27361d399f26a6f7462a82f9fbfd5d48>>
*/

/**
Expand Down Expand Up @@ -75,6 +75,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi

override fun enableFontScaleChangesUpdatingLayout(): Boolean = true

override fun enableIOSExperimentalAutoFocusImplementation(): Boolean = false

override fun enableIOSTextBaselineOffsetPerLine(): Boolean = false

override fun enableIOSViewClipToPaddingBox(): Boolean = false
Expand Down
Loading
Loading