Use a SwiftUI Remote Control To Drive a Tiny RealityKit Car

Daily Coding TIp 040

In the interest of full disclosure, this is Daily Coding Tip 040, but it’s been 43 days since this series started.

Most of those missing tips have occurred in the last week, as I’ve struggled to get more complicated AR examples done in time to post them every day.

I will catch up and make the numbers right again!

Before we get started with the remote control, we need to add the car we’re going to control. Create an Xcode project with the Augmented Reality template, and open the Experience.rcproject file in Reality Composer. This is where the default steel box is, but we’re going to want to remove it. Click the box, press delete, and click the + Add button in the top right. In the Transport category there’s a plane, taxi, fire engine and police car.

Choose whichever of these you want, and place it at the origin where the steel box was.

The properties panel on the right side has a field at the top for the name. Give the model a descriptive name. I chose the taxi, so I gave mine the name Taxi. This name will be accessible from code, so it’s important that you add it and don’t forget. If you choose a vehicle other than the taxi, you’ll need to change the part of my code that refers to it by this name.

Before we start writing SwiftUI, we need to extend some of the mathematical types. These extensions are based on an answer by Stack Overflow user Nativ, with the addition of a speed parameter in the movedForwardPosition function. Since the forward vector is a unit vector, it shows you how to move exactly 1 unit in the forward direction. In RealityKit, 1 unit is a 1 metre, which is way too far for us to be moving. Instead we’ll be multiplying it by a decimal speed that reduces the distance travelled substantially.

Now we can create the UI that will allow us to control the vehicle. Although the UI has no access to the ARView or the models it displays, we are passing closures for two situations. We need to take action when the rotation changes, and when a timer goes off. I’ve been pretty lazy and avoided setting an ARSessionDelegate, which would give us to the func session(ARSession, didUpdate: ARFrame) function.

This is a callback that occurs every time a new frame is about to be displayed, otherwise known as a frame update.

Instead of doing that, I’ve set a timer to perform an action 60 times a second. When an AR app is working well, it should run at 60 FPS, so this is close enough for our purposes. When the moving toggle is on, the vehicle will move at the speed specified by the speed slider.

Steering can be done by the rotation slider.

Now we need to create the onTimer and rotationChanged functions, which will be passed as closures to DriveView. I’m using AnyViewRepresentable again, but this time it takes a closure that will make the ARView automatically. The newOrientation computed property calculates a new angle in radians from the rotation property. The rotation property will be measured in degrees instead. The newOrientation property also converts this float value to a quaternion, which is what we need to set the rotation of the vehicle.

The ContentView structure has the boxAnchor as a constant, which is important because we referred to it in the onTimer and rotationChanged methods above. Because I didn’t rename the scene in Reality Composer from Box, this is still the automatically generated name of the function that loads it. It doesn’t matter much, and I thought it would be less complicated to do it this way.

All that’s left to do now is pass the @State bindings to the DriveView, and display the AR content in the AnyViewRepresentable.

If your app ends up being confined to a small square, instead of stretching to the size of the entire screen, you need to add a launch screen.

Create a new file in your project and give it the default name of Launch Screen.

Make sure to go to your project settings and specify the name of your launch screen file.

Get more Daily Coding Tips in your inbox!