mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Fix Switch layout with iOS26 (#53067)
Summary: 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: https://github.com/facebook/react-native/pull/53067 Test Plan: Tested locally with RNTester. | iOS Version | Before | After | | --- | --- | --- | | < iOS 26 |  |  | | >=--sanitized-- Rollback Plan: Differential Revision: D79653120 Pulled By: cipolleschi
This commit is contained in:
committed by
Facebook GitHub Bot
parent
453508ada8
commit
690fab6c4a
@@ -87,11 +87,11 @@ async function launchAppOnSimulator(appId, udid, isDebug) {
|
||||
|
||||
function startVideoRecording(jsengine, currentAttempt) {
|
||||
console.log(
|
||||
`Start video record using pid: video_record_${jsengine}_${currentAttempt}.pid`,
|
||||
`Start video record using pid: video_record_${currentAttempt}.pid`,
|
||||
);
|
||||
|
||||
const recordingArgs =
|
||||
`simctl io booted recordVideo video_record_${jsengine}_${currentAttempt}.mov`.split(
|
||||
`simctl io booted recordVideo video_record_${currentAttempt}.mov`.split(
|
||||
' ',
|
||||
);
|
||||
const recordingProcess = childProcess.spawn('xcrun', recordingArgs, {
|
||||
|
||||
@@ -264,7 +264,7 @@ const Switch: component(
|
||||
disabled,
|
||||
onTintColor: trackColorForTrue,
|
||||
style: StyleSheet.compose(
|
||||
{height: 31, width: 51},
|
||||
{alignSelf: 'flex-start' as const},
|
||||
StyleSheet.compose(
|
||||
style,
|
||||
ios_backgroundColor == null
|
||||
|
||||
@@ -424,6 +424,7 @@ let reactRCTFabric = RNTarget(
|
||||
let reactFabricComponents = RNTarget(
|
||||
name: .reactFabricComponents,
|
||||
path: "ReactCommon/react/renderer",
|
||||
// searchPaths: [ReactFBReactNativeSpecPath],
|
||||
excludedPaths: [
|
||||
"components/modal/platform/android",
|
||||
"components/modal/platform/cxx",
|
||||
@@ -442,7 +443,7 @@ let reactFabricComponents = RNTarget(
|
||||
"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.
|
||||
],
|
||||
dependencies: [.reactNativeDependencies, .reactCore, .reactJsiExecutor, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .yoga, .reactRendererDebug, .reactGraphics, .reactFabric, .reactTurboModuleBridging],
|
||||
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"]
|
||||
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"]
|
||||
)
|
||||
|
||||
/// React-FabricImage.podspec
|
||||
|
||||
+1
-1
@@ -9,10 +9,10 @@
|
||||
|
||||
#import <React/RCTConversions.h>
|
||||
|
||||
#import <react/renderer/components/FBReactNativeSpec/ComponentDescriptors.h>
|
||||
#import <react/renderer/components/FBReactNativeSpec/EventEmitters.h>
|
||||
#import <react/renderer/components/FBReactNativeSpec/Props.h>
|
||||
#import <react/renderer/components/FBReactNativeSpec/RCTComponentViewHelpers.h>
|
||||
#import <react/renderer/components/switch/AppleSwitchComponentDescriptor.h>
|
||||
|
||||
#import "RCTFabricComponentsPlugins.h"
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ Pod::Spec.new do |s|
|
||||
"react/renderer/components/scrollview/platform/cxx",
|
||||
"react/renderer/components/text/platform/cxx",
|
||||
"react/renderer/components/textinput/platform/ios",
|
||||
"react/renderer/components/switch/iosswitch",
|
||||
]);
|
||||
|
||||
add_dependency(s, "React-graphics", :additional_framework_paths => ["react/renderer/graphics/platform/ios"])
|
||||
|
||||
@@ -127,6 +127,13 @@ Pod::Spec.new do |s|
|
||||
sss.header_dir = "react/renderer/components/iostextinput"
|
||||
end
|
||||
|
||||
ss.subspec "switch" do |sss|
|
||||
sss.source_files = podspec_sources(
|
||||
["react/renderer/components/switch/iosswitch/**/*.{m,mm,cpp,h}"],
|
||||
["react/renderer/components/switch/iosswitch/**/*.h"])
|
||||
sss.header_dir = "react/renderer/components/switch/"
|
||||
end
|
||||
|
||||
ss.subspec "textinput" do |sss|
|
||||
sss.source_files = podspec_sources("react/renderer/components/textinput/*.{m,mm,cpp,h}", "react/renderer/components/textinput/**/*.h")
|
||||
sss.header_dir = "react/renderer/components/textinput"
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AppleSwitchShadowNode.h"
|
||||
|
||||
#include <react/renderer/core/ConcreteComponentDescriptor.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
/*
|
||||
* Descriptor for <Switch> component.
|
||||
*/
|
||||
class SwitchComponentDescriptor final
|
||||
: public ConcreteComponentDescriptor<SwitchShadowNode> {
|
||||
public:
|
||||
SwitchComponentDescriptor(const ComponentDescriptorParameters& parameters)
|
||||
: ConcreteComponentDescriptor(parameters) {}
|
||||
|
||||
void adopt(ShadowNode& shadowNode) const override {
|
||||
ConcreteComponentDescriptor::adopt(shadowNode);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <react/renderer/components/FBReactNativeSpec/EventEmitters.h>
|
||||
#include <react/renderer/components/FBReactNativeSpec/Props.h>
|
||||
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
extern const char IOSSwitchComponentName[];
|
||||
|
||||
// iOS Switch size is a constant, depending on the iOS version
|
||||
static Size iosSwitchSize{};
|
||||
|
||||
/*
|
||||
* `ShadowNode` for <IOSSwitch> component.
|
||||
*/
|
||||
class SwitchShadowNode final : public ConcreteViewShadowNode<
|
||||
IOSSwitchComponentName,
|
||||
SwitchProps,
|
||||
SwitchEventEmitter> {
|
||||
public:
|
||||
using ConcreteViewShadowNode::ConcreteViewShadowNode;
|
||||
|
||||
static ShadowNodeTraits BaseTraits() {
|
||||
auto traits = ConcreteViewShadowNode::BaseTraits();
|
||||
traits.set(ShadowNodeTraits::Trait::LeafYogaNode);
|
||||
traits.set(ShadowNodeTraits::Trait::MeasurableYogaNode);
|
||||
return traits;
|
||||
}
|
||||
|
||||
#pragma mark - LayoutableShadowNode
|
||||
|
||||
Size measureContent(
|
||||
const LayoutContext& layoutContext,
|
||||
const LayoutConstraints& layoutConstraints) const override;
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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>
|
||||
#include "AppleSwitchShadowNode.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
extern const char IOSSwitchComponentName[] = "Switch";
|
||||
|
||||
#pragma mark - LayoutableShadowNode
|
||||
|
||||
Size SwitchShadowNode::measureContent(
|
||||
const LayoutContext & /*layoutContext*/,
|
||||
const LayoutConstraints &layoutConstraints) const
|
||||
{
|
||||
if (iosSwitchSize.width != 0) {
|
||||
return iosSwitchSize;
|
||||
}
|
||||
// Let's cache the value of the SwitchSize the first time we compute it.
|
||||
__block CGSize cgsize;
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
cgsize = [UISwitch new].intrinsicContentSize;
|
||||
});
|
||||
|
||||
// The width returned by iOS is not exactly the width of the component.
|
||||
// For some reason, it is lacking 2 pixels. That can be seen clearly by setting a background
|
||||
// This is an iOS bug.
|
||||
iosSwitchSize = {.height = cgsize.height, .width = cgsize.width + 2};
|
||||
|
||||
return iosSwitchSize;
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 <AppKit/AppKit.h>
|
||||
#include "AppleSwitchShadowNode.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
extern const char IOSSwitchComponentName[] = "Switch";
|
||||
|
||||
#pragma mark - LayoutableShadowNode
|
||||
|
||||
Size SwitchShadowNode::measureContent(
|
||||
const LayoutContext & /*layoutContext*/,
|
||||
const LayoutConstraints &layoutConstraints) const
|
||||
{
|
||||
if (iosSwitchSize.width != 0) {
|
||||
return iosSwitchSize;
|
||||
}
|
||||
// Let's cache the value of the SwitchSize the first time we compute it.
|
||||
__block CGSize cgsize;
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
NSSwitch *switchControl = [[NSSwitch alloc] init];
|
||||
cgsize = [switchControl intrinsicContentSize];
|
||||
});
|
||||
|
||||
// For macOS, use the intrinsic size as-is
|
||||
iosSwitchSize = {.height = cgsize.height, .width = cgsize.width};
|
||||
|
||||
return iosSwitchSize;
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
+1
@@ -58,4 +58,5 @@ export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
|
||||
export default (codegenNativeComponent<SwitchNativeProps>('Switch', {
|
||||
paperComponentName: 'RCTSwitch',
|
||||
excludedPlatforms: ['android'],
|
||||
interfaceOnly: true,
|
||||
}): ComponentType);
|
||||
|
||||
Reference in New Issue
Block a user