CoreLocation을 통한 Beacon 감지 구현

Zeto·2023년 2월 8일
0

Swift_Framework

목록 보기
4/6

예정된 프로젝트와 관련해서 사용자의 위치 정보 및 UI 활성 여부를 조정할 위치 범위를 다뤄야 하는 부분이 있었는데 때마침 Beacon을 활용해보는 방법이 있어 먼저 테스트 프로젝트를 진행해보았다. 무언가 최신화된 자료를 찾아보기 힘들다보니 단편적인 정보들에 기대어 작업을 하게되었고, 이러한 난항들을 또 겪고 싶지 않아 간단하게나마 자료로 남겨두고자하여 글을 쓰게 되었다.

CoreLocation과 CLBeaconRegion

Beacon을 감지하는 기능을 사용하기 위해서는 CLBeaconRegion이 필수적으로 활용되어야하며, 당연히 위치 관련 기능이니 CoreLocation을 임포트해주어야만 한다. 그렇기에 귀찮지만 info.plist에 위치 활용에 대한 권한을 추가해주어야 한다.

이번에는 백그라운드에서도 감지할 수 있도록 관련 권한을 추가해주었다.
Beacon에 대한 내용으로 넘어가기 전, 권한 요청의 시기를 잠시만 짚고 넘어가고자 한다. 이렇게 백그라운드에서도 감지하기를 원하면 인증에 대한 요청을 requestWhenInUseAuthorization가 아닌, requestAlwaysAuthorization 메서드를 통해 진행한다.

다만 해당 메서드가 호출되어도 기존의 권한 요청과 다를 바 없는 Alert가 출력된다. 그럼 항상 허용은 대체 언제 할 수 있을까? 설정에 가서 직접 적용해줘야 하나??

항상 허용은 백그라운드에서도 위치를 감지하고 싶을 때 적용해주는 기능이다보니 해당 앱이 백그라운드로 들어간 상태에서 위치 정보가 바뀐 경우, 위치 사용을 항상으로 허용해줄 것인지에 대한 Alert가 출력된다. 다만 괜히 항상 허용하기 싫게 보이니, 꼭 허용해야할 것 같은 마음이 들도록 설명을 잘 추가해주는 것이 좋을 듯 하다.

CLBeaconRegion를 활용한 위치 감지

1. CLBeaconRegion의 생성

먼저 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만 넣어줘도 생성 가능하다.

2. CLBeaconRegion의 감지

이렇게 생성해준 CLBeaconRegionCLLocationManager를 통해서 감지하도록 해주어야 한다.

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
        }
    }
}

디테일한 거리까지는 알 수 없고 근처인지 먼지 정도만 구분할 수 있는 정도이다.

3. CLLocationManager 구현

이 부분은 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의 비콘이 전혀 감지되지 않는 것도 아니라서 어떤 구조인지 파악하기 어려울 뿐더러 이에 대한 별다른 내용을 찾을 수도 없어서 여전히 의문점 중 하나다.

profile
중2병도 iOS가 하고싶어

0개의 댓글