상세 컨텐츠

본문 제목

러닝 앱 개발기 (2) - CMMotionActivity를 활용해 사용자의 모션을 감지하기

러닝 앱 개발기

by Mr.Garlic 2021. 12. 9. 21:57

본문

CMMotionActivity를 사용해야 하는 이유, CoreMotion 사용해야 하는 이유

지난 편에서는 어떻게 지나온 경로를 맵 위에 표기했는지, 그리고 경로를 스크린샷 찍을 때 어떻게 영역을 설정했는지에 대해서 다루었는데요. 이번에는 사용자의 동작을 감지하는 방법에 대해서 이야기 해 보려고 합니다. 

 

아마 이전 편에서 다루었던 내용만 가지고 앱을 만드시면 장소 이동을 계속하는 경우에는 큰 문제가 없이 트래킹이 되지만, 한 장소에서 머무르는 경우에 크게 움직이지 않는데도 location이 update가 되면서 실제로는 뛰지 않았는데 이동한 경로가 막 뜰 수 있어요. 

 

만든 앱을 그냥 책상에 놓고 한 1시간 정도 있다가 켜서 확대해보시면 막 이렇게 난도질(?)이 되어있을 거예요. 이렇게 되는 이유는... 이제 디바이스가 신이 아니잖아요. 위치정보를 아무리 정확하게 받으려고 해도 오차가 있을 수 있는 거죠... 그러다 보니까 정확성이 생명인 러닝 앱이 앉아서 1시간 일했는데 막 2km 뛰었다고 나오는 불상사가 생겨버립니다 ㅠㅠ 

 

그로 인한 더 큰 부작용(?)은 장시간 자리에 뒀다가 켜게 되면 앱이 터질거예요. 저도 이유는 정확히 모르겠지만 polyline을 render 해주는데 한 화면에서 엄청 많은 선을 그려주려다 보니 터지는 것 같아요. 이렇게 생각하는 이유는 이 현상을 잡고 나서는 그렇게 터지는 일이 사라졌기 때문이에요. 

이렇게 막 그려져 있을 확률 99% 이걸 해결해 봅시다.

 

CMMotionActivity란? CoreMotion 사용할 준비하기

일단 애플 개발자 문서를 보면... 사용자의 모션이 바뀔 때 업데이트를 요청하는 객체라고 하네요! 

 

Apple Developer Documentation

 

developer.apple.com

 

이 객체를 사용해보도록 합시다! 간단하기 때문에 바로 코드를 보도록 할게요. 

import CoreMotion //코어 모션을 꼭 임포트 해주세요! 

class MapViewController: UIViewController {

	let motionManager = CMMotionActivityManager() //모션액티비티 매니저를 인스턴스로 만들어 줍시다.


	override func viewDidLoad() {
    super.viewDidLoad() 
    .
    .
    .
    motionManager.startActivityUpdates(to: .main) { activity in
            guard let activity = activity else {
                return
            }
            if activity == .running {
            	print("달리는 중입니다")
            }
			if activity == .walking {
            	print("걷는 중입니다")
            }
            
		}
        
}

참 간단하죠? 모션이 바뀔때마다 update가 되면서 달릴 때는 "달리는 중입니다"가, 걸을 때는 "걷는 중입니다"가 콘솔에 프린트될 거예요. 

 


CMMotionActivity의 종류

//참고! 이런 모양의 값을 리턴해 줍니다

CMMotionActivity @ 882319.763567,<startDate,2021-12-09 12:19:48 +0000,confidence,2,unknown,0,stationary,1,walking,1,running,0,automotive,0,cycling,0>

CMMotionActivity 타입은 이렇게 생겼어요. 

 

 

confidence : 마 니 자신 있나? 이게 아니고 이 모션에 대한 신뢰도라고 보시면 될 것 같아요. 

unknown: 이 모션이 뭔지 모르겠다는군요. 

stationary : 이게 좀 중요한데요! 어디에다가 거치해 둔 상태 또는 정지상태라고 생각하시면 됩니다. 가만~히 있어야 되고 생각보다 더 민감하게 변하더라고요. 그냥 책상에 올려두면 이 값이 true가 되고, 손에 들고 있기만 해도 제가 수전증이 없는데도 false가 나오기도 하더라고요. 진짜~ 가만히 있으면 true가 와요! 직접 디바이스 연결해서 보시면 좋을 것 같아요! 

walking: 걷는 상태

running: 뛰는 상태

automotive: 뭔가에 탑승해서 가는 상태, 자동차랄지 기차랄지... 

cycling: 자전거 타는 상태

 

 

그런데 예시를 보면 stationary 도 true, walking도 true 상태네요. 이건 뭘까요? 가만히 둬야 하는데 걷는 상태라는 게?

공식 문서에는 이렇게 적혀있어요! 

The motion-related properties of this class aren’t mutually exclusive. In other words, it’s possible for more than one of the motion-related properties to contain the value true. For example, if the user was driving in a car and the car stopped at a red light, the update event associated with that change in motion would have both the automotive and stationary properties set to true. It’s also possible for all of the properties to be set to false when the device is in motion but the movement doesn’t correlate to walking, running, cycling, or automotive travel.

 

(번역)

이 클래스의 모션과 관련한 프로퍼티들은 꼭 상호 독립적이어야 하는 것은 아닙니다! 다시 말하면, 1개 이상의 모션 관련 프로퍼티가 true일 수 있다는 거죠. 예를 들어, 사용자가 운전 중인데 빨간불이라 차가 멈췄어요! 이 경우에는 모션의 변화 이벤트를 업데이트해 줄 때 탑승상태 true, 정지상태 true가 될 수 있다는 거죠! 이런 것도 가능합니다! 디바이스가 확실히 움직이고 있긴 한데, 이게 뭐 걷는 것도 아니고 뛰는 것도 아니고 이상한 모양의 움직임이면 디바이스는 분명 움직이고 있긴 해도 모든 프로퍼티가 false일 수도 있는 거죠! 

 

어떤가요?? 참 재밌죠?? 그리고 이런 섬세한 모션 감지를 그냥 공짜로 쓸 수 있는 우리! 정말 행복하지 않나요? 희희

 

문제를 해결한 방법

위에서 얘기한 가만히 있는데도 막 ~~~ 위치정보가 여기저기서 업데이트되는 그런 문제는 복합적으로 해결을 할 수 있었어요. 

 

1. 모션 감지를 통해 뛰거나 걷는 경우에만 위치 정보를 업데이트하도록 처리! 다른 값과 상관없이 stationary일 때는 무조건 위치 정보를 업데이트하지 않도록 했어요. 

2. 위치 정보를 업데이트할 때에도 distanceFilter (이전 위치와 새로운 위치 사이의 직선거리가 몇 미터 이상일 때만 location을 update 하도록 조절할 수 있음)를 사용해서 10m 이하의 움직임은 무시하도록 처리! 

 


locationManager.distanceFilter = 10 
//10m 하셔도 되고 15 하셔도 되고 선택은 자유인데요! 너무 높게 잡으니까 코너를 도는 등 방향을 확 바꿨을 때 정확하게 나오지 않더라구요.

motionManager.startActivityUpdates(to: .main) { activity in
            guard let activity = activity else {
                return
            }
            if self.runMode == .running{ //runMode는 제가 따로 만든 열거형이니 똑같이 쓰시면 안돼요! 보통의 경우는 그냥 안쓰고 하시면 됩니다. 
                if activity.running == true || activity.walking == true {
                    if activity.stationary == false {
                        self.locationManager.startUpdatingLocation()
                        print("user motion is running or walking and not stationary")
                        print(activity)
                    } else {
                        self.locationManager.stopUpdatingLocation()
                        print("user motion is running or walking but stationary")
                        print(activity)
                    }
                    
                } else {
                    self.locationManager.stopUpdatingLocation()
                    print("user motion is not running or walking")
                    print(activity)
                }
            }
                    
        }

 

저는 이렇게 해서 제자리에 있어도 위치가 마구마구 변하는 그런 문제를 해결할 수 있었습니다. 

집에서 테스트할 때는 막 핸드폰 들고 흔들면서 테스트를 했어요... 빌드해서 핸드폰 들고나가서 야밤에 막 뛰어보고...

지금까지는 이 조합이 꽤 만족스럽습니다! 혹시 해보시면서 더 좋은 방법이나 더 좋은 조합 아시는 분 있으시면 

꼭 공유해주세요!! 도움이 되셨기를 바라며, 혹시 궁금한 점이 있으면 댓글에 자유롭게 남겨주세요. 감사합니다! 

 

 

 

 

관련글 더보기