CMPedometer
CMPedometer는 CoreMotion 프레임워크에 있는 클래스이다.
CoreMotion은 가속도계, 자이로스코프 등 여러 iOS 장치의 하드웨어에서 동작 관련 데이터를 제공할 수 있는 프레임워크이다.
처음에는 걸음 데이터를 받아오기 위해 어떤 클래스를 써야하는지 찾기 어려웠다. (CLLocation에는 CLLocationManager를 썼어서 CoreMotion에는 CMMotionManager를 쓰면 되는 줄 알았다는 ..)
공식 문서를 열심히 뒤적이다 발견한 것은 CMPedometer라는 클래스였다.
여기서 CMPedometer를 사용해서 데이터를 받아올 것이다!
CMPedometer의 문서를 읽어보면, CMPedometer 클래스는 시스템에서 생성한 실시간 걷기 데이터를 가져오기 위한 객체로 걸음수와 이동한 거리, 오르고 내린 층 수 등을 얻을 수 있다고 한다.
중요한 점은 모션 데이터는 실제 기기에서만 테스트가 가능하다.
권한 요청
이 API를 사용하려면 디바이스에 모션 데이터 접근 권한 요청을 해야한다. 문서에서는 Info.plist에 NSMotionUsageDescription 키를 포함해야 한다고 말하고 있다.
Info.plist를 열고 Key를 하나 추가해준 다음, Key에는 [Privacy - Motion Usage Description]을 넣고 Value에 권한 요청 시 띄울 문구를 쓴다. 아래 이미지에는 임시로 써놓았지만 실제로는 해당 데이터가 필요한 이유에 대해 자세히 써야한다.
이동거리, 평균 페이스 받아오기
먼저, CoreMotion을 import 해주어야 한다.
import CoreMotion
데이터를 받아오기 전에 해당 데이터를 측정할 수 있는지 확인해야 한다.
이동한 거리와 평균 페이스를 받아올 것이므로 `isDistanceAvailable()`과 `isPaceAvailable()`을 사용한다.
func checkDataIsAvailable() {
guard CMPedometer.isDistanceAvailable() && CMPedometer.isPaceAvailable() else {
print("거리 또는 페이스 측정 불가능")
return
}
}
Pedometer의 실시간 데이터를 받아오기 위해서 `startUpdates(from: )`을 사용한다.
문서에 `queryPedometerData(from: ,to: )`를 사용하라는 내용도 있는데 이건 이미 지나간 데이터를 받아오는 것이므로 실시간에 적합하지 않은 것 같다.
코드를 먼저 보면 다음과 같다.
// startDate는 정보 측정 시작 날짜 (Date 타입)
pedometer.startUpdates(from: startDate) { (pedometerData, error) in
guard let pedometerData = pedometerData, error == nil else {
print("data is nil")
return
}
DispatchQueue.main.async {
if let averagePace = pedometerData.averageActivePace,
let distance = pedometerData.distance
{
print(averagePace, distance)
}
}
}
`startUpdates()`와 함께 측정을 시작할 시간을 Date 타입으로 넣어주면 정보를 측정하기 시작한다. 핸들러로 pedometerData와 error를 받을 수 있고, pedometerData에서 이동거리(distance), 평균 페이스(averageActivePace), 현재 페이스(currentPace), 걸음수(numberOfSteps) 등 이외에도 많은 데이터들을 받을 수 있다.
테스트해봤을 때 currentPace는 항상 nil이 뜨던데 그건 이유를 모르겠다.
데이터의 단위는 아래와 같다.
- `averageActivePace` : 1m를 이동하는데 걸린 시간을 초단위로 반환
- `distance`: 이동한 거리를 m 단위로 반환
조금 응용(?) 해서 받은 데이터를 KM와 분 단위로 만들어본 코드는 아래와 같다.
let minPerKm = Int(averagePace.doubleValue * 1000) / 60
let secPerKm = Int(averagePace.doubleValue * 1000) % 60
print("Pace: \(minPerKm)\' \(secPerKm)\'\'")
print("distance \(distance.doubleValue / 1000)")
데이터 측정 멈추기
데이터 받는 것을 멈추고 싶을 때는 `stopUpdates()`를 사용한다.
pedometer.stopUpdates()
근데 이렇게 했을 때 데이터를 받아오는 주기가 3초..? 정도 됐던 것 같다. 내가 원하는 주기는 1초 이므로 1초마다 받아오도록 해보겠다.
Timer로 1초마다 데이터 받아오기
위 코드를 그냥 Timer에 넣어주기만 하면 된다. `withTimeInterval`로 주기를 조정할 수 있다.
데이터를 측정한 시간을 저장하고 싶다면 Int형 변수를 하나 만들어서 timer 안에서 1씩 증가시켜주면 된다. 아래 코드에서는 `secondsElapsed`가 그 역할을 한다.
var timer: Timer?
private var secondsElapsed = 0 // 경과한 시간 저장
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { [weak self] _ in
guard let self = self else { return }
secondsElapsed += 1
pedometer.startUpdates(from: startDate) { (pedometerData, error) in
guard let pedometerData = pedometerData, error == nil else {
print("data is nil")
return
}
DispatchQueue.main.async {
if let averagePace = pedometerData.averageActivePace,
let distance = pedometerData.distance
{
print(averagePace, distance)
}
}
}
데이터 수집을 멈출 때 타이머도 마찬가지로 stop을 해줘야한다.
func stopTimer() {
timer?.invalidate()
timer = nil
}
이렇게 CMPedomer를 활용해서 사용자의 실시간 걸음 데이터를 받아오는 것을 구현해봤다.
막상 구현하고 보니 별게 없지만 구현 시작할 때는 참고할 수 있는 자료가 많이 없어서 많이 헤맸다. 공식문서에 많이 의존을 했고 GPT도 도움을 꽤 받은 것 같다.
참고할 자료가 많다면 구현하기는 쉽겠지만 이렇게 자료가 많이 없어서 공식문서에 의존을 하게 될 때 오히려 더 기억에 잘 남게 된다.
이 글도 열심히 공식문서를 보다가 어려움 느낀 누군가에게 도움이 되었으면 좋겠다. : )
'iOS' 카테고리의 다른 글
[iOS] Thread와 GCD(Grand Central Dispatch) 기본 (1) | 2024.11.24 |
---|---|
동기(Sync)와 비동기(Async)에 대한 이해 (5) | 2024.11.07 |
[iOS] Naver Map SDK Swift Package 만들기 (3) | 2024.09.07 |