From b080fb98c58e9cd88e870d87ee22d2f6c4709172 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Tue, 7 Apr 2026 13:48:10 +0200 Subject: [PATCH] docs: add docs for swift --- docs/pages/create.md | 2 + docs/pages/swift-new-architecture.md | 186 +++++++++++++++++++++++++++ docs/rspress.config.ts | 10 ++ 3 files changed, 198 insertions(+) create mode 100644 docs/pages/swift-new-architecture.md diff --git a/docs/pages/create.md b/docs/pages/create.md index 4fbffd415..8c8c68d2b 100644 --- a/docs/pages/create.md +++ b/docs/pages/create.md @@ -93,3 +93,5 @@ Once the project is created, you can follow the official React Native docs to le - [Native UI Components for iOS](https://reactnative.dev/docs/legacy/native-components-ios) - [Turbo Modules](https://reactnative.dev/docs/turbo-native-modules-introduction) - [Fabric Components](https://reactnative.dev/docs/fabric-native-components-introduction) + +Turbo Modules and Fabric components don't have native support for Swift. If you want to write the iOS implementation in Swift for a Turbo Module or Fabric View, see [Swift with Turbo Modules and Fabric](./swift-new-architecture.md). diff --git a/docs/pages/swift-new-architecture.md b/docs/pages/swift-new-architecture.md new file mode 100644 index 000000000..455bfe1e4 --- /dev/null +++ b/docs/pages/swift-new-architecture.md @@ -0,0 +1,186 @@ +--- +title: Swift with Turbo Modules and Fabric +--- + +For Turbo Modules and Fabric views, React Native expects the iOS entry point to stay in Objective-C. If you want to use Swift, you need to create a separate Swift class for the implementation and update our Objective-C code to act as a thin wrapper that forwards calls to the Swift implementation. + +## Calling Swift from Objective-C + +Swift can be called from Objective-C as long as the API is Objective-C compatible: + +- Mark the class or exposed methods with `@objc` +- Inherit from `NSObject` or `UIView` +- Use types that Objective-C can bridge, such as `String`, `NSNumber`, `NSArray`, `NSDictionary`, and `UIColor` + +The Objective-C wrapper also needs to import the generated Swift compatibility header, which is usually named `"-Swift.h"`: + +```objc +#if __has_include("/-Swift.h") +#import "/-Swift.h" +#else +#import "-Swift.h" +#endif +``` + +The `#if __has_include` check is necessary to make the code work with `use_frameworks!` in the `Podfile`, which changes the import path for the generated header. + +## Turbo Modules + +To use Swift for a Turbo Module, you can create a Swift class (e.g. `Impl`) that contains the actual implementation of your module's methods. Then, update the Objective-C code (`.mm`) to use that Swift class. + +Example Swift implementation for a Turbo Module that multiplies two numbers: + +```swift +import Foundation +@objc(Impl) +final class Impl: NSObject { + @objc + func multiply(_ a: Double, b: Double) -> NSNumber { + NSNumber(value: a * b) + } +} +``` + +Then call this implementation from the Objective-C wrapper: + +```objc +#import ".h" + +// Add the import for the generated Swift header +// [!code highlight:5] +#if __has_include("/-Swift.h") +#import "/-Swift.h" +#else +#import "-Swift.h" +#endif + +// Declare a private property for the Swift implementation +// [!code highlight:3] +@implementation { + Impl *_impl; +} + +// Initialize the Swift class on module creation +// [!code highlight:8] +- (instancetype)init +{ + if (self = [super init]) { + _impl = [Impl new]; + } + return self; +} + +// Call the Swift implementation for the actual implementation +// [!code highlight:4] +- (NSNumber *)multiply:(double)a b:(double)b +{ + return [_impl multiply:a b:b]; +} + +// Keep rest of the boilerplate for module registration +- (std::shared_ptr)getTurboModule: + (const facebook::react::ObjCTurboModule::InitParams &)params +{ + return std::make_sharedSpecJSI>(params); +} + ++ (NSString *)moduleName +{ + return @""; +} + +@end +``` + +## Fabric Views + +To use Swift for a Fabric view, you can create a Swift `UIView` subclass (e.g. `ViewImpl`) that contains the actual implementation of your view. Then, update the generated Objective-C code (`View.mm`) to use that Swift view. + +Example Swift implementation for a Fabric view that applies a `color` prop: + +```swift +import UIKit + +@objc(ViewImpl) +final class ViewImpl: UIView { + @objc + func setColor(_ color: UIColor?) { + backgroundColor = color + } +} +``` + +Then call this implementation from the generated Objective-C++ wrapper: + +```objc +#import "View.h" + +// Add the import for the generated Swift header +// [!code highlight:5] +#if __has_include("/-Swift.h") +#import "/-Swift.h" +#else +#import "-Swift.h" +#endif + +#import + +#import ViewSpec/ComponentDescriptors.h> +#import ViewSpec/Props.h> +#import ViewSpec/RCTComponentViewHelpers.h> + +#import "RCTFabricComponentsPlugins.h" + +using namespace facebook::react; + +// Declare a private property for the Swift view +// [!code highlight:3] +@implementation View { + ViewImpl *_view; +} + +// Keep the boilerplate for Fabric registration +// [!code highlight:4] ++ (ComponentDescriptorProvider)componentDescriptorProvider +{ + return concreteComponentDescriptorProvider<ViewComponentDescriptor>(); +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + static const auto defaultProps = + std::make_sharedViewProps>(); + _props = defaultProps; + + // Initialize the Swift view when the Fabric view is created + _view = [ViewImpl new]; // [!code highlight] + + self.contentView = _view; + } + + return self; +} + +- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps +{ + const auto &oldViewProps = + *std::static_pointer_cast<ViewProps const>(_props); + const auto &newViewProps = + *std::static_pointer_cast<ViewProps const>(props); + + if (oldViewProps.color != newViewProps.color) { + // Call methods on the Swift view when props are updated + // It may be necessary to convert some types before passing them to Swift + [_view setColor:RCTUIColorFromSharedColor(newViewProps.color)]; // [!code highlight] + } + + [super updateProps:props oldProps:oldProps]; +} + +@end +``` + +## Notes + +If Xcode does not pick up a new Swift file immediately, rerun `pod install` in `example/ios` and restart Xcode. diff --git a/docs/rspress.config.ts b/docs/rspress.config.ts index 0eb1ffd5f..e215cddbe 100644 --- a/docs/rspress.config.ts +++ b/docs/rspress.config.ts @@ -1,5 +1,6 @@ import { defineConfig } from '@rspress/core'; import { withCallstackPreset } from '@callstack/rspress-preset'; +import { transformerNotationHighlight } from '@shikijs/transformers'; export default withCallstackPreset( { @@ -28,10 +29,19 @@ export default withCallstackPreset( { text: 'Scaffold a library', link: '/create' }, { text: 'Build a library', link: '/build' }, { text: 'ESM support', link: '/esm' }, + { + text: 'Swift with Turbo Modules and Fabric', + link: '/swift-new-architecture', + }, { text: 'FAQ', link: '/faq' }, ], }, }, + markdown: { + shiki: { + transformers: [transformerNotationHighlight()], + }, + }, base: '/react-native-builder-bob/', }) );