Example of calling swift code from RN

This commit is contained in:
Artur Gurgul 2025-08-03 12:53:34 +02:00
parent 1a9884e0e9
commit 151ed6d78b
12 changed files with 86 additions and 6 deletions

30
App.tsx
View file

@ -1,12 +1,13 @@
import { NewAppScreen } from '@react-native/new-app-screen' import { NewAppScreen } from '@react-native/new-app-screen'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { StatusBar, Text, StyleSheet, useColorScheme, ScrollView } from 'react-native' import { StatusBar, Text, StyleSheet, useColorScheme, ScrollView, Button, Alert } from 'react-native'
import { NativeEventEmitter, NativeModules } from 'react-native' import { NativeEventEmitter, NativeModules } from 'react-native'
const { Emitter } = NativeModules const { Emitter, TestingServiceModule } = NativeModules
import { requireNativeComponent } from 'react-native' import { requireNativeComponent } from 'react-native'
import type { StyleProp, ViewStyle } from 'react-native' import type { StyleProp, ViewStyle } from 'react-native'
type CustomButtonProps = { type CustomButtonProps = {
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
} }
@ -15,9 +16,23 @@ const BaseButton = requireNativeComponent<CustomButtonProps>('BaseButton')
export default function App() { export default function App() {
const isDarkMode = useColorScheme() === 'dark' const isDarkMode = useColorScheme() === 'dark'
const [message, setMessage] = useState(null) const [message, setMessage] = useState<string|null>(null)
const [color, setColor] = useState("#FFF") const [color, setColor] = useState("#FFF")
const onPress = async () => {
if (!TestingServiceModule?.greet) {
Alert.alert('Module not found', 'MyNativeModule is not linked or not exported.')
return
}
try {
const result = await TestingServiceModule.greet('John')
setMessage(result);
} catch (e) {
//Alert.alert('Error', String(e?.message ?? e))
}
}
useEffect(() => { useEffect(() => {
const emitter = new NativeEventEmitter(Emitter); const emitter = new NativeEventEmitter(Emitter);
const subscription = emitter.addListener('onMessage', (event) => { const subscription = emitter.addListener('onMessage', (event) => {
@ -28,6 +43,14 @@ export default function App() {
setColor("#00F") setColor("#00F")
} }
} }
TestingServiceModule.greet('John')
.then((message: string) => {
setMessage(message)
})
.catch((error: string) => {
console.error(error);
});
}) })
return () => { return () => {
@ -41,6 +64,7 @@ export default function App() {
<Text>{message ?? 'Waiting for message...'}</Text> <Text>{message ?? 'Waiting for message...'}</Text>
<CustomButton style={{ height: 200 }} /> <CustomButton style={{ height: 200 }} />
<BaseButton style={{ height: 200 }} /> <BaseButton style={{ height: 200 }} />
<Button title="Call Swift greet()" onPress={onPress} />
</ScrollView> </ScrollView>
); );
} }

View file

@ -1,2 +1,3 @@
#import <React/RCTViewManager.h> #import <React/RCTViewManager.h>
#import <React/RCTBridgeModule.h>

View file

@ -0,0 +1,31 @@
//
// TestingService.swift
// RNPlayground
//
// Created by Artur Gurgul on 03/08/2025.
//
import Foundation
import React
@objc(TestingServiceModule)
class TestingServiceModule: NSObject {
@objc
func greet(_ name: String,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
let message = "Hello, \(name)! From Swift."
resolve(message)
}
@objc
func constantsToExport() -> [AnyHashable: Any]! {
return ["greeting": "Hello from Swift"]
}
@objc
static func requiresMainQueueSetup() -> Bool {
return false
}
}

View file

@ -0,0 +1,17 @@
//
// TestingServiceModuleBridge.m
// RNPlayground
//
// Created by Artur Gurgul on 03/08/2025.
//
#import <React/RCTBridgeModule.h>
// This tells React Native about the Swift class
@interface RCT_EXTERN_MODULE(TestingServiceModule, NSObject)
RCT_EXTERN_METHOD(greet:(NSString *)name
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
@end

View file

@ -16,7 +16,7 @@ final class SharedState: ObservableObject {
private let emitter = EventEmitter.sharedInstance private let emitter = EventEmitter.sharedInstance
// SwiftUI => RN => SwiftUI // SwiftUI => RN => SwiftUI
@Published var text1: String = "" @Published var message: String = ""
func send(message: String) { func send(message: String) {
emitter.send(message: message) emitter.send(message: message)

View file

@ -12,7 +12,7 @@ struct BaseButton: View {
var body: some View { var body: some View {
Button("SwiftUI Button") { Button("SwiftUI Button") {
sharedState.text1 = "Clicked in SwiftUI button that was created in RN" sharedState.message = "Clicked in SwiftUI button that was created in RN"
} }
} }
} }

View file

@ -1,3 +1,10 @@
//
// CustomButtonManager.m
// RNPlayground
//
// Created by Artur Gurgul on 02/08/2025.
//
#import <React/RCTViewManager.h> #import <React/RCTViewManager.h>
@interface RCT_EXTERN_MODULE(CustomButtonManager, RCTViewManager) @interface RCT_EXTERN_MODULE(CustomButtonManager, RCTViewManager)

View file

@ -18,7 +18,7 @@ struct ToolboxHeader: View {
sharedState.send(message: "hello from Swift!") sharedState.send(message: "hello from Swift!")
} }
} }
Text("SwiftUI => RN => SwiftUI: \(sharedState.text1)") Text("Message: \(sharedState.message)")
} }
} }
} }