Skip to content

Commit ba51aea

Browse files
cipolleschifacebook-github-bot
authored andcommitted
Fix Switch layout with iOS26 (#53247)
Summary: Pull Request resolved: #53247 Apple changed the sizes of the UISwitchComponent and now, if you build an iOs app using the <Switch> component, the layout of the app will be broken because of wrong layout measurements. This has been reported also by [https://github.com/facebook/react-native/issues/52823](https://github.com/facebook/react-native/issues/52823). The `<Switch>` component was using hardcoded values for its size. This change fixes the problem by: - Using codegen for interface only - Implementing a custom Sadow Node to ask the platform for the Switch measurements - Updating the JS layout to wrap the size around the native component. ## Changelog: [iOS][Fixed] - Fix Switch layout to work with iOS26 Pull Request resolved: #53067 Test Plan: Tested locally with RNTester. | iOS Version | Before | After | | --- | --- | --- | | < iOS 26 | https://github.com/user-attachments/assets/91d73ea3-30ba-4a5c-948e-ea5c63aa7c6d | https://github.com/user-attachments/assets/76061bc8-0f14-412a-a8fb-d1c3951772e6 | | >= iOS 26 | https://github.com/user-attachments/assets/1abc477f-bc0a-4762-938e-98814fb2a054 | https://github.com/user-attachments/assets/77e562e1-b803-46ac-9cf6-102f062a1cd4 | Rollback Plan: Reviewed By: sammy-SC Differential Revision: D79653120 Pulled By: cipolleschi fbshipit-source-id: d99b353b7b7b5496b148779de4abe3e57dd38156
1 parent 24657ad commit ba51aea

File tree

11 files changed

+154
-2
lines changed

11 files changed

+154
-2
lines changed

packages/react-native/Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,7 @@ let reactFabricComponents = RNTarget(
433433
"components/view/platform/android",
434434
"components/view/platform/windows",
435435
"components/view/platform/macos",
436+
"components/switch/iosswitch/react/renderer/components/switch/MacOSSwitchShadowNode.mm",
436437
"components/textinput/platform/android",
437438
"components/text/platform/android",
438439
"components/textinput/platform/macos",
@@ -445,7 +446,7 @@ let reactFabricComponents = RNTarget(
445446
"conponents/rncore", // this was the old folder where RN Core Components were generated. If you ran codegen in the past, you might have some files in it that might make the build fail.
446447
],
447448
dependencies: [.reactNativeDependencies, .reactCore, .reactJsiExecutor, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .yoga, .reactRendererDebug, .reactGraphics, .reactFabric, .reactTurboModuleBridging],
448-
sources: ["components/inputaccessory", "components/modal", "components/safeareaview", "components/text", "components/text/platform/cxx", "components/textinput", "components/textinput/platform/ios/", "components/unimplementedview", "components/virtualview", "components/virtualviewexperimental", "textlayoutmanager", "textlayoutmanager/platform/ios"]
449+
sources: ["components/inputaccessory", "components/modal", "components/safeareaview", "components/text", "components/text/platform/cxx", "components/textinput", "components/textinput/platform/ios/", "components/unimplementedview", "components/virtualview", "components/virtualviewexperimental", "textlayoutmanager", "textlayoutmanager/platform/ios", "components/switch/iosswitch"]
449450
)
450451

451452
/// React-FabricImage.podspec

packages/react-native/React/Base/RCTUtils.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ RCT_EXTERN CGFloat RCTScreenScale(void);
5353
RCT_EXTERN CGFloat RCTFontSizeMultiplier(void);
5454
RCT_EXTERN CGSize RCTScreenSize(void);
5555
RCT_EXTERN CGSize RCTViewportSize(void);
56+
RCT_EXTERN CGSize RCTSwitchSize(void);
5657

5758
// Round float coordinates to nearest whole screen pixel (not point)
5859
RCT_EXTERN CGFloat RCTRoundPixelValue(CGFloat value);

packages/react-native/React/Base/RCTUtils.mm

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,16 @@ CGSize RCTViewportSize(void)
410410
return window ? window.bounds.size : RCTScreenSize();
411411
}
412412

413+
CGSize RCTSwitchSize(void)
414+
{
415+
static CGSize rctSwitchSize;
416+
static dispatch_once_t onceToken;
417+
dispatch_once(&onceToken, ^{
418+
rctSwitchSize = [UISwitch new].intrinsicContentSize;
419+
});
420+
return rctSwitchSize;
421+
}
422+
413423
CGFloat RCTRoundPixelValue(CGFloat value)
414424
{
415425
CGFloat scale = RCTScreenScale();

packages/react-native/React/Fabric/Mounting/ComponentViews/Switch/RCTSwitchComponentView.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99

1010
#import <React/RCTConversions.h>
1111

12-
#import <react/renderer/components/FBReactNativeSpec/ComponentDescriptors.h>
1312
#import <react/renderer/components/FBReactNativeSpec/EventEmitters.h>
1413
#import <react/renderer/components/FBReactNativeSpec/Props.h>
1514
#import <react/renderer/components/FBReactNativeSpec/RCTComponentViewHelpers.h>
15+
#import <react/renderer/components/switch/AppleSwitchComponentDescriptor.h>
1616

1717
#import "RCTFabricComponentsPlugins.h"
1818

packages/react-native/React/React-RCTFabric.podspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ Pod::Spec.new do |s|
7575
"react/renderer/components/scrollview/platform/cxx",
7676
"react/renderer/components/text/platform/cxx",
7777
"react/renderer/components/textinput/platform/ios",
78+
"react/renderer/components/switch/iosswitch",
7879
]);
7980

8081
add_dependency(s, "React-graphics", :additional_framework_paths => ["react/renderer/graphics/platform/ios"])

packages/react-native/ReactCommon/React-FabricComponents.podspec

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,14 @@ Pod::Spec.new do |s|
127127
sss.header_dir = "react/renderer/components/iostextinput"
128128
end
129129

130+
ss.subspec "switch" do |sss|
131+
sss.source_files = podspec_sources(
132+
["react/renderer/components/switch/iosswitch/**/*.{m,mm,cpp,h}"],
133+
["react/renderer/components/switch/iosswitch/**/*.h"])
134+
sss.exclude_files = "react/renderer/components/switch/iosswitch/**/MacOS*.{m,mm,cpp,h}"
135+
sss.header_dir = "react/renderer/components/switch/"
136+
end
137+
130138
ss.subspec "textinput" do |sss|
131139
sss.source_files = podspec_sources("react/renderer/components/textinput/*.{m,mm,cpp,h}", "react/renderer/components/textinput/**/*.h")
132140
sss.header_dir = "react/renderer/components/textinput"
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include "AppleSwitchShadowNode.h"
11+
12+
#include <react/renderer/core/ConcreteComponentDescriptor.h>
13+
14+
namespace facebook::react {
15+
16+
/*
17+
* Descriptor for <Switch> component.
18+
*/
19+
class SwitchComponentDescriptor final
20+
: public ConcreteComponentDescriptor<SwitchShadowNode> {
21+
public:
22+
SwitchComponentDescriptor(const ComponentDescriptorParameters& parameters)
23+
: ConcreteComponentDescriptor(parameters) {}
24+
25+
void adopt(ShadowNode& shadowNode) const override {
26+
ConcreteComponentDescriptor::adopt(shadowNode);
27+
}
28+
};
29+
30+
} // namespace facebook::react
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <react/renderer/components/FBReactNativeSpec/EventEmitters.h>
11+
#include <react/renderer/components/FBReactNativeSpec/Props.h>
12+
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
13+
14+
namespace facebook::react {
15+
16+
extern const char AppleSwitchComponentName[];
17+
18+
/*
19+
* `ShadowNode` for <IOSSwitch> component.
20+
*/
21+
class SwitchShadowNode final : public ConcreteViewShadowNode<
22+
AppleSwitchComponentName,
23+
SwitchProps,
24+
SwitchEventEmitter> {
25+
public:
26+
using ConcreteViewShadowNode::ConcreteViewShadowNode;
27+
28+
static ShadowNodeTraits BaseTraits() {
29+
auto traits = ConcreteViewShadowNode::BaseTraits();
30+
traits.set(ShadowNodeTraits::Trait::LeafYogaNode);
31+
traits.set(ShadowNodeTraits::Trait::MeasurableYogaNode);
32+
return traits;
33+
}
34+
35+
#pragma mark - LayoutableShadowNode
36+
37+
Size measureContent(
38+
const LayoutContext& layoutContext,
39+
const LayoutConstraints& layoutConstraints) const override;
40+
};
41+
42+
} // namespace facebook::react
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import <React/RCTUtils.h>
9+
#import <UIKit/UIKit.h>
10+
#include "AppleSwitchShadowNode.h"
11+
12+
namespace facebook::react {
13+
14+
extern const char AppleSwitchComponentName[] = "Switch";
15+
16+
#pragma mark - LayoutableShadowNode
17+
18+
Size SwitchShadowNode::measureContent(
19+
const LayoutContext & /*layoutContext*/,
20+
const LayoutConstraints & /*layoutConstraints*/) const
21+
{
22+
CGSize uiSwitchSize = RCTSwitchSize();
23+
return {.width = uiSwitchSize.width, .height = uiSwitchSize.height};
24+
}
25+
26+
} // namespace facebook::react
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import <AppKit/AppKit.h>
9+
#include "AppleSwitchShadowNode.h"
10+
11+
namespace facebook::react {
12+
13+
extern const char AppleSwitchComponentName[] = "Switch";
14+
15+
#pragma mark - LayoutableShadowNode
16+
17+
Size SwitchShadowNode::measureContent(
18+
const LayoutContext & /*layoutContext*/,
19+
const LayoutConstraints & /*layoutConstraints*/) const
20+
{
21+
static CGSize nsSwitchSize = CGSizeZero;
22+
static dispatch_once_t onceToken;
23+
dispatch_once(&onceToken, ^{
24+
dispatch_sync(dispatch_get_main_queue(), ^{
25+
nsSwitchSize = [NSSwitch new].intrinsicContentSize;
26+
});
27+
});
28+
29+
return {.width = nsSwitchSize.width, .height = nsSwitchSize.height};
30+
}
31+
32+
} // namespace facebook::react

0 commit comments

Comments
 (0)