예정된 프로젝트와 관련해서 사용자의 위치 정보 및 UI 활성 여부를 조정할 위치 범위를 다뤄야 하는 부분이 있었는데 때마침 Beacon을 활용해보는 방법이 있어 먼저 테스트 프로젝트를 진행해보았다. 무언가 최신화된 자료를 찾아보기 힘들다보니 단편적인 정보들에 기대어 작업을 하게되었고, 이러한 난항들을 또 겪고 싶지 않아 간단하게나마 자료로 남겨두고자하여 글을 쓰게 되었다.
CoreLocation과 CLBeaconRegion
Beacon을 감지하는 기능을 사용하기 위해서는 CLBeaconRegion
이 필수적으로 활용되어야하며, 당연히 위치 관련 기능이니 CoreLocation
을 임포트해주어야만 한다. 그렇기에 귀찮지만 info.plist
에 위치 활용에 대한 권한을 추가해주어야 한다.
이번에는 백그라운드에서도 감지할 수 있도록 관련 권한을 추가해주었다.
Beacon에 대한 내용으로 넘어가기 전, 권한 요청의 시기를 잠시만 짚고 넘어가고자 한다. 이렇게 백그라운드에서도 감지하기를 원하면 인증에 대한 요청을 requestWhenInUseAuthorization
가 아닌, requestAlwaysAuthorization
메서드를 통해 진행한다.
다만 해당 메서드가 호출되어도 기존의 권한 요청과 다를 바 없는 Alert가 출력된다. 그럼 항상 허용은 대체 언제 할 수 있을까? 설정에 가서 직접 적용해줘야 하나??
항상 허용은 백그라운드에서도 위치를 감지하고 싶을 때 적용해주는 기능이다보니 해당 앱이 백그라운드로 들어간 상태에서 위치 정보가 바뀐 경우, 위치 사용을 항상으로 허용해줄 것인지에 대한 Alert가 출력된다. 다만 괜히 항상 허용하기 싫게 보이니, 꼭 허용해야할 것 같은 마음이 들도록 설명을 잘 추가해주는 것이 좋을 듯 하다.
먼저 CLBeaconRegion
을 생성하기 위해서는 감지하고 싶은 Beacon의 UUID를 먼저 알고 있어야 한다. 이와 함께 major와 minor 값도 추가로 설정하여 더욱 세분화된 CLBeaconRegion
으로 만들어줄 수 있다.
보통은 UUID는 쇼핑몰 같은 한 지역 범위 단위로 보고, major는 각각의 층수, minor는 해당 층수에 있는 매대에 지정하는 번호 정도의 분류로 보면 된다.
let uuid: UUID = .init(uuidString: "fda50693-a4e2-4fb1-afcf-c6eb07647825")!
let beaconRegion: CLBeaconRegion = .init(proximityUUID: uuid, major: 1, minor: 1, identifier: "Beacon")
이런식으로 위에서 얘기한 값들을 활용하여 생성이 가능한데, iOS 13이후부터는 생성 방법이 다소 달라졌다. UUID, major, minor로 바로 CLBeaconRegion
을 생성하는 것이 아닌, 먼저 CLBeaconIdentityConstraint
라는 객체를 생성하고 이를 인자로 써서 CLBeaconRegion
을 생성해주는 방식으로 바뀌었다.
let uuid: UUID = .init(uuidString: "fda50693-a4e2-4fb1-afcf-c6eb07647825")!
let beaconIdentityConstraint: CLBeaconIdentityConstraint = .init(uuid: uuid)
let beaconRegion: CLBeaconRegion = .init(beaconIdentityConstraint: beaconIdentityConstraint, identifier: "MyBeacon")
CLBeaconIdentityConstraint
는 Beacon ID의 특성을 지정해서 제약 조건을 만드는 것으로 해당 제약 조건을 가진 CLBeaconRegion
객체다라는 형태로 생성해주는 듯 하다. 참고로 major와 minor는 선택 사항이라 UUID만 넣어줘도 생성 가능하다.
이렇게 생성해준 CLBeaconRegion
을 CLLocationManager
를 통해서 감지하도록 해주어야 한다.
self.locationManger?.startMonitoring(for: beaconRegion)
startMonitoring(for:)
이라는 메서드에 생성한 객체를 인자값으로 넣어주면 자동으로 Beacon을 모니터링하기 시작한다. 다만 해당 메서드는 이름 그대로 해당 Beacon의 지역을 모니터링하면서 범위 내에 사용자가 위치하는지 아닌지에 대한 정보만 출력해준다.
extension ViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) {
print("DidStartMonitoring")
}
func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
print("DidDetermineState")
switch state {
case .unknown:
print("Unknown determine state")
case .inside:
print("Inside determine state")
case .outside:
print("Outside determine state")
}
}
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
print("DidEnterRegion")
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
print("DidExitRegion")
}
}
startMonitoring(for:)
메서드를 실행하면 호출되는 메서드들이다. didStartMonitoringFor
은 모니터링이 시작됐을 때, didDetermineState
는 모니터링 중인 지역 범위 안인지 밖인지를 체크해주고 아래의 didEnterRegion
, didExitRegion
는 범위에 들어가거나 나갈 때 호출이 된다.
CLBeaconRegion
의 거리 정보를 알 수 있으려면 startRangingBeacons(satisfying:)
을 호출해줘야 한다.
extension ViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didRange beacons: [CLBeacon], satisfying beaconConstraint: CLBeaconIdentityConstraint) {
let beacon = beacons[0]
switch beacon.proximity {
case .unknown:
case .immediate:
case .near:
case .far:
@unknown default:
break
}
}
}
디테일한 거리까지는 알 수 없고 근처인지 먼지 정도만 구분할 수 있는 정도이다.
이 부분은 CLBeaconRegion
을 활용하기 위해 필수적으로 필요한 요소를 구현하는 부분이라 간단하게만 짚고 넘어가려한다.
self.locationManger?.allowsBackgroundLocationUpdates = true
self.locationManger?.pausesLocationUpdatesAutomatically = false
백그라운드에서도 위치 정보를 계속해서 업데이트하기 위해서는 업데이트를 허용하도록 true를 주고, 자동으로 위치 업데이트를 멈추지 않도록 false를 주는 설정해주어야 한다.
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .notDetermined:
self.locationManger?.requestAlwaysAuthorization()
case .restricted, .denied:
break
case .authorizedWhenInUse, .authorizedAlways:
self.startMonitoring()
@unknown default:
break
}
}
또한 CLLocationManagerDelegate
가 할당되면 locationManagerDidChangeAuthorization
가 자동으로 호출되니 각각의 권한 상태에 따라서 적절한 작업을 진행하도록 구현하면 된다. .restricted, .denied의 경우에는 설정창으로 이동하도록 해주면 좋다.
완료된 작업에 대한 예시 및 전체 코드는 깃허브에 올려두었다.
동일한 UUID의 비콘 중 하나만 UUID가 다른 비콘이 있어 둘 모두 모니터링 및 감지가 가능했다. 다만 이 경우에는 locationManager(_:didRange:satisfying:)
에서 비콘의 개수를 호출할 때, 최초에 등록한 UUID의 비콘들의 개수만 출력되는 듯 했다.
그렇다고 다른 UUID의 비콘이 전혀 감지되지 않는 것도 아니라서 어떤 구조인지 파악하기 어려울 뿐더러 이에 대한 별다른 내용을 찾을 수도 없어서 여전히 의문점 중 하나다.