Using Core Motion within a SwiftUI application

Using Core Motion within a SwiftUI application

Understand how to use sensor data in your SwiftUI app using the Core Motion framework.

To access a device’s accelerometer, gyroscopes, and, when available, the pedometer, magnetometer, and barometer sensors, whether on iOS, iPadOS, watchOS, or visionOS, use the Core Motion framework. This framework reports motion and environment data from the integrated hardware sensors. Using it within an app created with SwiftUI to create visual interaction based on the collected data is quite simple.

Keep in mind that to test the features of an app that uses the Core Motion framework, you must run the application on the device. In the following example, we are simulating dynamic depth using the gyroscope data to rotate the UI elements and move their shadow.

Step 1 - Movement management

The first step is to import the Core Motion framework.

import SwiftUI
import CoreMotion

Then, create a class responsible for providing the data from the device sensors. The class MotionDataProvider, conforming with the ObservableObject protocol, will store the motion data from the device sensor and publish any updates based on the device's movement.

class MotionDataProvider: ObservableObject {
    // 1.
    private let motionProvider = CMMotionManager()
    // 2.
    @Published var x = 0.0
    @Published var y = 0.0

    init() {
        // 3.
        motionProvider.deviceMotionUpdateInterval = 1 / 20

        motionProvider.startDeviceMotionUpdates(to: .main) { [weak self] data, error in
            // 4.
            guard let motion = data?.attitude else { return }
            self?.x = motion.roll
            self?.y = motion.pitch
        }
    }  
}
  1. The motionProvider constant is an instance of CMMotionManager, the object responsible for starting and managing motion services.
  2. The properties x and y will be updated by the motion manager object, they are tagged with the @Published property wrapper.
  3. When initializing the MotionDataProvider object, set up the motion manager object:
    • .deviceMotionUpdateInterval: An instance property that provides device motion updates at regular intervals, in seconds, determined by the received value.
    • .startDeviceMotionUpdates: An instance method to get the latest device motion data without a block handler.

      To update the UI, the .main parameter indicates that the data will be processed in the main queue and [weak self] ensures that if not needed, the current object is not kept in memory, avoiding retain cycles.

      The data and error values contain information about the device. The data and any possible errors found, respectively.
  4. Check if there is data to be read and access the attitude value, which contains the orientation of the device relative to a reference frame at a point in time, of the type CMAttitude. The roll value is assigned to the x property, representing a rotation around a longitudinal axis. The pitch value is assigned to the y property, representing a rotation around a lateral axis.

Step 2 - Create movement in the view

In a SwiftUI view, let’s use the MotionDataProvider to get the information from the device sensors and use it to promote changes in the user interface.

struct ContentView: View {
    // 1.
    @StateObject private var motionManager = MotionDataProvider()
    
    var body: some View {
        VStack {
            Text("Create with Swift")
                .font(.system(size: 40).bold())
            
            ZStack{
                Circle()
                        .frame(width: 200)
                Image(systemName: "swift")
                    .font(.system(size: 100).bold())
            }
        }
        // 2.
        .foregroundStyle(
            .white.gradient.shadow(
                .inner(color: .black, radius: 6, x: motionManager.x * -20, y: motionManager.y * -20)
            )
        )
        // 4.
        .rotation3DEffect(.degrees(motionManager.x * 20), axis: (x: 0, y: 1, z: 0))
        .rotation3DEffect(.degrees(motionManager.y * 20), axis: (x: -1, y: 0, z: 0))
        
    }
}
  1. The motionManager property is an instance of the MotionDataProvider and is constantly publishing the values read by the CMMotionManager.
  2. The depth effect inside the view components will be achieved with the shadow modifier. The coordinate properties will receive the values from the motionManager to calculate the shadow values.
  3. Apply a rotation effect on the x and y axis of the view using the rotation3DEffect modifier. The calculation of the value of the rotation uses the motionManager data to define the amount of degrees to rotate the view.

Final Result

Once all the steps are complete, you can run the tests on a device, such as an iPhone. The expected result is that as you move the device, the shadows of the objects in the view will move to give us the sensation of depth, as shown in the example:

0:00
/0:14

Here is the complete code:

import SwiftUI
import CoreMotion

class MotionDataProvider: ObservableObject {
    
    private let motionProvider = CMMotionManager()
    
    @Published var x = 0.0
    @Published var y = 0.0
    
    init() {
        motionProvider.deviceMotionUpdateInterval = 1 / 20
        motionProvider.startDeviceMotionUpdates(to: .main) { [weak self] data, error in
            guard let motion = data?.attitude else { return }
            
            self?.x = motion.roll
            self?.y = motion.pitch
        }
    }
    
}

struct ContentView: View {
    // 1.
    @StateObject private var motionManager = MotionDataProvider()
    
    var body: some View {
        // 2.
        VStack {
            Text("Create with Swift")
                .font(.system(size: 40).bold())
            
            ZStack{
                Circle().frame(width: 200) //.shadow(radius: 8)
                
                Image(systemName: "swift")
                    .font(.system(size: 100).bold())
            }
        }
        // 3.
        .foregroundStyle(
            .white.gradient.shadow(
                .inner(color: .black, radius: 6, x: motionManager.x * -20, y: motionManager.y * -20)
            )
        )
        // 4.
        .rotation3DEffect(.degrees(motionManager.x * 20), axis: (x: 0, y: 1, z: 0))
        .rotation3DEffect(.degrees(motionManager.y * 20), axis: (x: -1, y: 0, z: 0))
        
    }
}

If you want to go deeper into the CoreMotion framework, how it works and what more you can do with it, check the following: