2024-02-13
Extending React Native visionOS with RealityKit
Immersive Space with Snow Particles
Contents
Introduction
React Native possesses a unique capability that enables the blending of Native code with JavaScript as needed. In today's blog post, we will explore how to enhance your spatial application with immersive features using Swift and the RealityKit framework.
If you are not familiar with React Native for visionOS, I recommend starting with this blog post: Announcing React Native for Apple Vision Pro.
Our goal is to create a Mixed Reality Immersive Space that generates snow particles:
Disclaimer: The main focus of this blog post is to describe how to integrate custom RealityKit code into React Native visionOS, not how to use RealityKit. If you want to dive deeper into this topic, checkout this WWDC talk.
Getting started
First let's initialize a new project:
npx @callstack/react-native-visionos@latest init YourApp
Next, go to YourApp/visionos
folder and run following commands to install Pods:
bundle install && bundle exec pod install
Open the project in Xcode and click Run
xed visionos/YourApp.xcworkspace
JavaScript part
Let's start with implementing JavaScript part. Navigate to App.tsx
and add basic markup with two buttons and text.
import React from 'react';
import {Alert, Button, StyleSheet, Text, View} from 'react-native';
function App(): React.JSX.Element {
return (
<View style={styles.container}>
<Text style={styles.title}>React native visionOS 👋</Text>
<Button
title="Open ImmersiveSpace"
onPress={openImmersiveSpace}
color="white"
/>
<Button
title="Close ImmersiveSpace"
onPress={closeImmersiveSpace}
color="white"
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
title: {
fontSize: 30,
color: 'white',
fontWeight: 'bold',
marginBottom: 15,
},
});
export default App;
Next, let's implement methods to open and close immersive spaces:
import {XR} from '@callstack/react-native-visionos';
import React from 'react';
import {Alert, Button, StyleSheet, Text, View} from 'react-native';
function App(): React.JSX.Element {
const [isOpen, setIsOpen] = useState(false) // Store if session is open
const openImmersiveSpace = async () => {
try {
await XR.requestSession('SnowEmitter'); // Pass uniquie ID
setIsOpen(true)
} catch (error) {
if (error instanceof Error) {
Alert.alert('Error', error.message); // Handle errors
}
}
};
const closeImmersiveSpace = async () => {
await XR.endSession();
setIsOpen(false)
};
return (
//..
);
}
This code calls XR.requestSession()
passing a unique identifier of ImmersiveSpace that we will implement in the next section.
Warning: XR API doesn't store information whether session is open, you need to track it yourself.
Swift Part
Inside of App.swift
add new ImmersiveSpace
. Use the same identifier as in JavaScript part.
@main
struct SnowEmitterAppApp: App {
@UIApplicationDelegateAdaptor var delegate: AppDelegate
@State private var immersionStyle: ImmersionStyle = .mixed
var body: some Scene {
RCTMainWindow(moduleName: "SnowEmitterApp")
.defaultSize(CGSize(width: 500, height: 800))
ImmersiveSpace(id: "SnowEmitter") { // Match ID from JS
SnowEmitterView()
}
.immersionStyle(selection: $immersionStyle, in: .mixed, .full)
}
}
You can learn more about ImmersiveSpaces here: https://developer.apple.com/documentation/swiftui/immersive-spaces
Next, let's implement SnowEmitterView
. Create a new Swift file and paste below snippet:
import SwiftUI
import RealityKit
struct SnowEmitterView: View {
var body: some View {
RealityView { content in
let particleEntity = Entity() // Create new Entity
var particles = ParticleEmitterComponent.Presets.snow // Pick snow preset
// Set properties
particles.emitterShape = .plane
particles.birthLocation = .surface
particles.mainEmitter.size = 0.003
// Add Particle emitter component
particleEntity.components[ParticleEmitterComponent.self] = particles
// Set position and scale
particleEntity.position = SIMD3(x: 0, y: 2, z: -2)
particleEntity.scale *= 3
particleEntity.transform.scale *= 3
// Add particle entity
content.add(particleEntity)
}
}
}
This view uses RealityView
and adds a new Entity
with a ParticleEmitterComponent
.
You can read more about Particle Emitters, here.
That's all
And that's it! Now you have App UI written in React Native and spatial extension in RealityKit. I hope you found this article useful. If you have any questions or feedback feel free to reach out to me on Twitter.
For those interested in the sample code, you can find it in the GitHub Repository.