프로젝트를 진행하면서 화면에 올라간 View를 자이로스코프를 이용하여 조금씩 다르게 보여주려고 하는데요
(마치 예전 아이폰 배경화면이 휴대폰을 조금씩 기울이면 화면도 움직이는 것처럼. iOS 16부터는 안되나..?)
그래서 자이로스코프 센서를 이용하는 방법에 대해 알아보겠습니다
Core Motion Framework는 iOS, watchOS 기기의 위치, 방향의 변화를 감지할 수 있게 해줘요.
iPhone, iPad, Apple Watch의 보조 프로세서 덕분에 CPU나 배터리의 큰 소모 없이 데이터를 읽고 처리할 수 있는 아주 편리한 친구입니당
가속도계와 자이로스코프 데이터는 아래 그림과 같이 3D 좌표 공간에 투영됩니다
가상의 x, y, z축을 디바이스에 꼽고, 오른손으로 감싸쥐면 좌표계에 대한 이해는 끝납니다 😲
CMMotionManager 클래스를 통해 기기의 움직임에 대한 데이터를 제공받을 수 있어요
그런데 CMMotionManager를 사용할 때 한가지 주의할 점!
바로 인스턴스는 여러개 사용하지 말고 하나만 사용하는 것이 좋다는 것인데요
만약 인스턴스를 여러개 사용하게 된다면 데이터 측정이 정확하지 않을 수 있다고 해요
그렇다면 CMMotionManager를 통해 어떤 데이터를 읽어올 수 있을까요?
오호.. 자기계에 대한 정보도 받아올 수 있군요!!😬
위 3개는 각 센서가 개별적으로 데이터를 읽는 친구들이지만, 이들을 하나로 합친 Device motion
이란 친구도 있습니다! (정확하진 않지만.. 여튼 여러 데이터를 같이 받아오기 위해서는 인스턴스를 여러개 만들면 안되니까 이 친구를 사용하면 될 것 같아요)
Device-motion data, indicating key motion-related attributes such as the device's user-initiated acceleration, its attitude, rotation rates, orientation relative to calibrated magnetic fields, and orientation relative to gravity. This data is provided by Core Motion’s sensor fusion algorithms.
The processed device-motion data gives the device’s attitude, rotation rate, calibrated magnetic fields, the direction of gravity, and the acceleration the user is imparting to the device.
이번에는 주로 자이로스코프에 대해서 사용을 해보려고 해요
요즘의 Apple 기기들의 대부분은 표준 센서를 가지고 있지만, 그럼에도 모션 데이터를 읽기 전에 해당 기기가 기능을 사용할 수 있는 기기인지 확인을 하는 단계가 있으면 좋아요
사용 가능한 디바이스인지 확인하는 코드는 아래와 같이 작성할 수 있습니다
let motionManager = CMMotionManager()
guard motionManager.isAccelerometerAvailable else { return }
guard motionManager.isGyroAvailable else { return }
guard motionManager.isMagnetometerAvailable else { return }
guard motionManager.isDeviceMotionAvailable else { return }
이전 단계에서 CMMotionManager를 사용할 수 있는 디바이스인지를 검사한 후 드디어 디바이스의 Motion 관련 데이터를 받아올 수 있어요
데이터는 periodic하게 받거나, 설정한 interval마다 받을 수 있습니다
Periodic하게 데이터에 접근하기 위해서는 우선 원하는 데이터에 대해서 업데이트를 시작해야 합니다.
그리고 나서는 CMMotionManager
의 get-only 프로퍼티를 통해 기기의 motion 관련 데이터에 언제든지 접근할 수 있어요.
또한 isActive 프로퍼티를 통해 지금 사용이 가능한지도 확인할 수 있어요
periodic한 방식은 주로 게임 앱에 주로 사용이 된다고 해요. 왜냐하면 대부분의 게임 앱은 가장 마지막의 motion data를 필요로 하기 때문이죠
// 우선 Update를 시작합니다
motionManager.startAccelerometerUpdates()
motionManager.startGyroUpdates()
motionManager.startMagnetometerUpdates()
// 그리고 motion 관련 데이터에 접근할 수 있어요
motionManager.accelerometerData
motionManager.gyroData
motionManager.magnetometerData
// 또한 지금 사용이 가능한지도 확인할 수 있어요
motionManager.isAccelerometerActive
motionManager.isGyroActive
motionManager.isMagnetometerActive
motionManager.isDeviceMotionActive
// 마지막으로 업데이트를 멈춥니다
motionManager.stopAccelerometerUpdates()
motionManager.stopGyroUpdates()
motionManager.stopMagnetometerUpdates()
motionManager.stopDeviceMotionUpdates()
Interval하게 데이터를 처리하기 위해서 사용하는 업데이트를 시작하는 메소드는
실행할 thread가 어디인지 OperationQueue
와 데이터를 어떻게 처리할지 작성하는 클로저를 인자로 받아요
OperationQueue
는 main
과 current
를 사용할 수 있습니당
그리고 전달된 클로저는 업데이트 주기마다 호출됩니다..!!
따라서 얼마만큼 자주 데이터를 수집할 것인지 설정을 먼저 해줘야겠네요
interval은 하드웨어마다 최대 interval이 다르긴 하지만, 일반적으로 최소 100Hz는 최댓값으로 지원한다고 해요
만약 하드웨어가 지원하는 최댓값보다 더 높은 수치를 interval로 설정한다면, CoreMotion이 자동으로 하드웨어가 지원하는 만큼으로 알아서 해준다고 하네요 ㅎㅎ
아래 예시에서는 Gyro 데이터와 관련한 메소드만 작성해볼게요
motionManager.gyroUpdateInterval = 0.1 // 0.1초마다 데이터를 수집합니다
motionManager.startGyroUpdates(to: .main) { [weak self] (data, error) in
guard let data = data, error == nil else { return }
// 데이터 처리하기
}
motionManager.stopGyroUpdates()
queue를 사용할 때에는 모두 main에서 처리하는 것보다 UI 업데이트를 할 때에만 main을 사용하는 편이 좋습니다
따라서 아래와 같이 코드를 조금 개선할 수 있겠네요
let queue = DispatchQueue(label: "motion") // DispatchQueue는 Operating Queue 타입으로 전달될 수 없어요 ㅠ
motionManager.startGyroUpdates(to: queue) { [weak self] (data, error) in
guard let data = data, error == nil else { return }
// 데이터 처리하기
DispatchQueue.main.async {
// UI 처리하기
}
}
motionManager.stopGyroUpdates()
인줄 알았으나!!!!
흠 막상 코드를 쳐보니
에러가 났어요 ㅠㅠ
자세한 내용은 Operating Queue와 GCD 관련한 글에서 확인하는걸로 하고...
흠.. 아무래도 글이 길어질 것 같으니 실제로 사용하는것도 다음에 해보도록 하죠 😅
Apple Developer Documentation - CMMotionManager
Apple Developer Documentation - Getting Raw Gyroscope Events
CMDeviceMotion