Swift에서 Beacon을 사용하는 방법

Younkyum JIN
10 min readAug 1, 2024

--

실내에서 위치를 파악할 수 있는 방법에는 여러가지가 있습니다. 그 중iBeacon을 사용하는 것은 굉장히 간단합니다.

Apple Developer Document , iBeacon

최근, 프로젝트 개발을 위하여 SwiftUI를 기반으로 iBeacon을 사용하게 되었습니다. iBeacon은 Bluetooth를 기반으로 거리 정보 등을 제공하는 방식으로, 말 그대로 비콘이며, iPhone은 이 Beacon을 감지할 수도, 자신이 그 Beacon이 될 수도 있습니다.

About iBeacon

iBeacon은 Apple에서 발표한 저전력 블루투스(BLE) 기반의 근거리 통신 방식입니다. 이 때 iBeacon은 다음의 값들만 가집니다.

class iBeacon {
var uuid: UUID
var major: Int
var minor: Int
var identifier: Identifier
...
}

iBeacon은 유니크한 값인 UUID를 가지고, MajorMinor, 그리고 마찬가지로 정보를 전달하는 identifier를 가집니다.

이 때, UUID 값은 해당 iBeacon을 감지하기 위해서 반드시 필요합니다.

이러한 정보를 기반으로 iBeacon을 감지하면, 감지된 정보는 Proximity로 표현되며, 총 4개의 값을 가집니다.

enum Proximity {
case .unknown // == 0
case .far // == 3
case .near // == 2
case .immediate // == 1
}

따라서, 정확한 거리 값을 알기는 어렵습니다. 다만, 각각의 값 별로 “예상되는" 거리는 다음과 같습니다.

  • far: 2m ~ 30m
  • near: 50cm ~ 2m
  • immediate: 0cm ~ 50cm

그럼 이제, iBeacon을 만들고 추적해보겠습니다.

iPhone을 iBeacon으로 만들기

우선, 당신이 iBeacon과 관련된 어플리케이션을 만들고 싶다면, 다음 두 조건 중 하나를 만족해야 합니다.

  • UUID를 알고 있는 iBeacon 기기를 보유 중임.
  • iPhone 2개를 보유 중임.

만약 둘 중 어디에도 속하지 않는다면, iPhone 보다는 iBeacon을 구매하시기를 추천드립니다. 가격에 50배 정도 차이가 나거든요.

하지만 만약 당신이 iPhone 2개를 보유 중이라면, 다음과 같이 해당 iPhone을 iBeacon으로 만들 수 있습니다.

다만 코드를 작성하고 실행하기 전에, 반드시 Bluetooth와 관련된 권한 확보요청을 info.plist에 작성하는 것을 잊지 마세요.

import SwiftUI
import CoreBluetooth
import CoreLocation

class BeaconTransmitter: NSObject, ObservableObject, CBPeripheralManagerDelegate {
var peripheralManager: CBPeripheralManager?
var beaconRegion: CLBeaconRegion?
var beaconIdentityConstraint: CLBeaconIdentityConstraint?

override init() {
super.init()

let uuid = UUID(uuidString: "UUID")!
let major: CLBeaconMajorValue = 1000
let minor: CLBeaconMinorValue = 1
let beaconID = " "

beaconIdentityConstraint = CLBeaconIdentityConstraint(uuid: uuid, major: major, minor: minor)
beaconRegion = CLBeaconRegion(beaconIdentityConstraint: beaconIdentityConstraint!, identifier: beaconID)
peripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: nil)
}

func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if peripheral.state == .poweredOn {
startAdvertising()
} else {
stopAdvertising()
}
}

func startAdvertising() {
guard let beaconRegion = beaconRegion else { return }

let peripheralData = beaconRegion.peripheralData(withMeasuredPower: nil)
peripheralManager?.startAdvertising(((peripheralData as NSDictionary) as! [String: Any]))
}

func stopAdvertising() {
peripheralManager?.stopAdvertising()
}
}

길다고 생각될 수도 있지만, 굉장히 간단한 코드입니다.

BeaconTransmitterNSObject, ObservableObject, CBPeripheralManagerDelegate를 준수하는 클래스입니다.

여기서 CBPeripheralManagerDelegate는 기기의 Bluetooth를 활용하기 위해서 사용되며, 주변 기기와의 통신을 Bluetooth를 통해 진행하기 위해서 사용됩니다.

peripheralManagerDidUpdateState 메소드는 블루투스의 전원을 공급할 수 있는 상황인지 판단하고, 가능하다면 startAdvertising을 시작하여 iPhone이 iBeacon으로 사용될 수 있도록 합니다.

startAdvertising 메소드는 이미 생성자 단에서 결정된 beaconRegion 정보를 이용하여 iBeacon과 동일하게 BLE 통신을 위한 전파를 뿌리도록 합니다.

Class를 기반으로 iPhone을 iBeacon으로 사용할 수 있고, 이제 이 iBeacon을 인식할 차례입니다.

iPhone에서 iBeacon을 인식하기

이 또한 위의 코드와 같이 매우 간단합니다.

iBeacon을 인식하기 위해서 필요한 요소는 추적하고자 하는 iBeacon의 UUID 값 입니다. 만약 이를 알 수 없다면, iPhone은 iBeacon을 추적할 수 없습니다.

따라서 UUID 값을 잘 메모해두도록 합시다.

해당 iBeacon의 UUID를 확인했다면, 다음과 같이 목표하는 iBeacon을 추적할 수 있습니다.

import Foundation
import CoreLocation

class BeaconManager: NSObject, ObservableObject, CLLocationManagerDelegate {
private var locationManager: CLLocationManager
@Published var detectedBeacons: [CLBeacon] = []

override init() {
self.locationManager = CLLocationManager()
super.init()
self.locationManager.delegate = self
self.locationManager.requestWhenInUseAuthorization()
}

func startScanning() {
let beaconRegion = CLBeaconRegion(uuid: UUID(uuidString: "당신의 UUID")!,
identifier: "동일하지 않아도 인식합니다.")
self.locationManager.startMonitoring(for: beaconRegion)
self.locationManager.startRangingBeacons(satisfying: CLBeaconIdentityConstraint(uuid: beaconRegion.uuid))
}

func stopScanning() {
let beaconRegion = CLBeaconRegion(uuid: UUID(uuidString: "당신의 UUID")!,
identifier: "동일하지 않아도 인식합니다.")
self.locationManager.stopMonitoring(for: beaconRegion)
self.locationManager.stopRangingBeacons(satisfying: CLBeaconIdentityConstraint(uuid: beaconRegion.uuid))
}

func locationManager(_ manager: CLLocationManager, didRange beacons: [CLBeacon], satisfying beaconConstraint: CLBeaconIdentityConstraint) {
}

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedAlways || status == .authorizedWhenInUse {
startScanning()
} else {
stopScanning()
}
}
}

여기서 가장 중요한 점 중 하나는 바로, Bluetooth를 사용하지만 CoreBluetooth를 사용하는 것이 아닌 CoreLocation을 사용한다는 점입니다.

따라서, info.plist에서 Location Usage에 대한 권한을 받아오는 것을 잊지 마시기 바랍니다.

Monitoring 과 Ranging

Beacon 사용에 있어 start|stop Monitoringstart|stop RangingBeacons는 함수 이름으로만 보았을 때 크게 달라보이지 않을 수 있습니다.

다만, 두 메소드는 각각 다른 역할을 담당합니다.

  • Monitoring의 경우: Monitoring은 기기가 인지 가능한 범위 안에 Beacon이 들어왔는지 판단합니다. 즉, Beacon이 내뿜고 있는 전파를 인지할 수 있는지 판단합니다.
  • Ranging의 경우: Ranging의 경우 Monitoring이 이미 진행된(전파를 인지한) Beacon에 대해서 거리를 측정합니다. 이는 CLBeacon 객체 안의 proximity 프로퍼티를 통해 파악할 수 있습니다.

또한, 이는 둘의 매개변수에서도 차이를 볼 수 있습니다.

  • BeaconRegion의 경우: BeaconRegion은 단순히 인식이 되었는지 여부를 판단하기 위해서 사용되는 객체입니다. 따라서 해당 기기의 UUID만으로 선언이 가능합니다.
  • BeaconIdentityConstraints의 경우: IdentityConstraints의 경우 Beacon의 Identifier, Major, Minor 등의 정보를 가지고 있습니다. 위의 코드에서는 따로 작성하지 않지만, 추적되는 Beacon과 정보를 교환한 뒤에는 Major, Minor 등의 세부적인 정보를 통해서 비콘과 기기 간 거리를 측정하는데에 사용됩니다.

이를 통해, 간단하고 쉬운 여러가지 프로덕트를 만드는데에 도움이 되기를 바랍니다.

--

--

Younkyum JIN

Product Manager of Briefing, Treehouse | Learner of Apple Developer Academy @ POSTECH