iOS Beacon Detection Snippet
Guide to getting started with the AreaMetrics beacon monitoring platform.

Add the AreaMetrics.swift file to your project

AreaMetrics.swift
1
//
2
// AreaMetrics.swift
3
// AreaMetricsSnippet v1.7
4
// Copyright © 2018 AreaMetrics. All rights reserved.
5
//
6
7
import Foundation
8
import UIKit
9
import CoreLocation
10
import AdSupport
11
12
class AreaMetrics: NSObject, CLLocationManagerDelegate {
13
14
// MARK: Singleton Declaration
15
16
static let sharedInstance = AreaMetrics()
17
override private init() {}
18
19
// MARK: Simple Properties
20
21
let locationManager = CLLocationManager()
22
let venueUUIDs: Set = ["6CA0C73C-F8EC-4687-9112-41DCB6F28879",
23
"50765CB7-D9EA-4E21-99A4-FA879613A492",
24
"9EB353A0-69B6-4947-B710-BAE643C8BCA5",
25
"74278BDA-B644-4520-8F0C-720EAF059935",
26
"5993A94C-7D97-4DF7-9ABF-E493BFD5D000",
27
"56DB0365-A001-4062-9E4D-499D3B8ECCF3",
28
"F1EABF09-E313-4FCD-80DF-67C779763888",
29
"B8FED863-9F1C-447C-8F82-DF0C2E067DEA",
30
"E2C56DB5-DFFB-48D2-B060-D0F5A71096E0",
31
"B9407F30-F5F8-466E-AFF9-25556B57FE6D",
32
"A8D278E4-35B5-44EF-9CAC-0F3FC511FE3E",
33
"F2356731-FBD4-4A10-8AA2-C89ADF48A98D",
34
"FDA50693-A4E2-4FB1-AFCF-C6EB07647825",
35
"F7826DA6-4FA2-4E98-8024-BC5B71E0893E",
36
"D77657C4-52A7-426F-B9D0-D71E10798C8A"]
37
let amUUIDs: Set = ["B9407F30-F5F8-466E-AFF9-25556B577272"]
38
let defaultSession = URLSession(configuration: .default)
39
var lastSentBeacons = [String:TimeInterval]()
40
var baseURL = "https://api.areametrics.com/v3/"
41
var amPubID = ""
42
let AM_BATCH = "AM_BATCH"
43
let AM_BATCH_LAST_SEND = "AM_BATCH_LAST_SEND"
44
let SNIPPET_VERSION = "1.7"
45
var currentlySendingBatch = false
46
47
// MARK: Lazy Stored Properties to eliminate repeat calculations
48
49
lazy var vendorId: String? = {
50
return UIDevice.current.identifierForVendor?.uuidString
51
}()
52
53
lazy var adId: String? = {
54
guard ASIdentifierManager.shared().isAdvertisingTrackingEnabled else {
55
return nil
56
}
57
return ASIdentifierManager.shared().advertisingIdentifier.uuidString
58
}()
59
60
lazy var locale: String? = {
61
return Locale.current.regionCode?.uppercased()
62
}()
63
64
lazy var gdprUser: Bool = {
65
let gdprCountries: Set = ["AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE", "GB", "IS", "LI", "NO"]
66
if locale != nil && gdprCountries.contains(locale!) {
67
return true
68
}
69
return false
70
}()
71
72
lazy var deviceModel: String = {
73
var systemInfo = utsname()
74
uname(&systemInfo)
75
let machineMirror = Mirror(reflecting: systemInfo.machine)
76
let identifier = machineMirror.children.reduce("") { identifier, element in
77
guard let value = element.value as? Int8, value != 0 else { return identifier }
78
return identifier + String(UnicodeScalar(UInt8(value)))
79
}
80
return identifier
81
}()
82
83
lazy var deviceAgent: String? = {
84
var webView = UIWebView(frame: CGRect.zero)
85
let agent = webView.stringByEvaluatingJavaScript(from: "navigator.userAgent")
86
return agent
87
}()
88
89
// MARK: - Initial Configuration
90
91
enum AMException: Error {
92
case runtimeError(String)
93
}
94
95
func startService(pubID: String) {
96
if !pubID.hasSuffix("00") {
97
print("AreaMetrics startService called with invalid Publisher ID! Snippet is not initialized.")
98
return
99
}
100
101
amPubID = pubID
102
locationManager.delegate = self
103
sendUserInit()
104
if CLLocationManager.authorizationStatus() == .authorizedAlways {
105
startMonitoring()
106
}
107
print("AreaMetrics Initialized Successfully!")
108
}
109
110
private func startMonitoring() {
111
if gdprUser {
112
stopMonitoring()
113
return
114
}
115
116
let uuids = amUUIDs.union(venueUUIDs)
117
for uuid in uuids {
118
let region = CLBeaconRegion(proximityUUID: UUID(uuidString: uuid)!, identifier: uuid)
119
region.notifyOnEntry = true
120
region.notifyEntryStateOnDisplay = true
121
locationManager.startMonitoring(for: region)
122
}
123
}
124
125
func stopMonitoring() {
126
let uuids = amUUIDs.union(venueUUIDs)
127
for uuid in uuids {
128
let region = CLBeaconRegion(proximityUUID: UUID(uuidString: uuid)!, identifier: uuid)
129
locationManager.stopMonitoring(for: region)
130
}
131
}
132
133
// MARK: - LocationManagerDelegate Implementation
134
135
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
136
if status == .authorizedAlways {
137
startMonitoring()
138
}
139
}
140
141
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
142
if region is CLBeaconRegion {
143
let beaconRegion = region as! CLBeaconRegion
144
locationManager.startRangingBeacons(in: beaconRegion)
145
}
146
}
147
148
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
149
if region is CLBeaconRegion {
150
let beaconRegion = region as! CLBeaconRegion
151
locationManager.stopRangingBeacons(in: beaconRegion)
152
}
153
}
154
155
func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
156
let nowTime = Date().timeIntervalSince1970
157
for beacon in beacons {
158
let prox = beacon.proximityUUID.uuidString
159
let space: TimeInterval = amUUIDs.contains(prox) ? 5 : 5
160
if let lastSentTime = lastSentBeacons[beaconId(beacon)] {
161
if nowTime - lastSentTime > space {
162
addBeaconToBatch(beacon)
163
}
164
} else {
165
addBeaconToBatch(beacon)
166
}
167
}
168
}
169
170
// MARK: - Caching and sending decision logic
171
172
private func addBeaconToBatch(_ beacon: CLBeacon) {
173
lastSentBeacons[beaconId(beacon)] = Date.init().timeIntervalSince1970
174
175
var beaconJSON: [String: Any] = [:]
176
beaconJSON["time"] = Int64(Date.init().timeIntervalSince1970)
177
beaconJSON["uuid"] = beacon.proximityUUID.uuidString
178
beaconJSON["major"] = beacon.major
179
beaconJSON["minor"] = beacon.minor
180
beaconJSON["rssi"] = beacon.rssi
181
beaconJSON["beacon_accuracy"] = beacon.accuracy
182
beaconJSON["proximity"] = beacon.proximity.rawValue
183
beaconJSON["btype"] = "ibeacon"
184
185
let prefs = UserDefaults.standard
186
var batch = prefs.array(forKey: AM_BATCH) ?? []
187
batch.append(beaconJSON)
188
let batchOverflow = batch.count - 100
189
if batchOverflow > 0 {
190
batch.removeFirst(batchOverflow)
191
}
192
prefs.set(batch, forKey: AM_BATCH)
193
checkWhetherBatchReadyAndSend()
194
}
195
196
private func checkWhetherBatchReadyAndSend() {
197
let prefs = UserDefaults.standard
198
let batch = prefs.array(forKey: AM_BATCH) ?? []
199
if batch.count > 0 {
200
let lastSend = prefs.double(forKey: AM_BATCH_LAST_SEND)
201
if Date.init().timeIntervalSince1970 - lastSend > 1800 {
202
sendBeaconBatchToServer(batch)
203
}
204
}
205
}
206
207
private func clearBeaconBatch() {
208
UserDefaults.standard.removeObject(forKey: AM_BATCH)
209
}
210
211
private func markBeaconBatchSent() {
212
let prefs = UserDefaults.standard
213
prefs.set(Date.init().timeIntervalSince1970, forKey: AM_BATCH_LAST_SEND)
214
}
215
216
// MARK: - Network Activity
217
218
private func getPostString(params:[String:Any]) -> String {
219
var data = [String]()
220
for(key, value) in params {
221
data.append(key + "=\(value)")
222
}
223
return data.map{String($0)}.joined(separator: "&")
224
}
225
226
private func sendBeaconBatchToServer(_ batch: Array<Any>) {
227
if currentlySendingBatch {
228
return
229
}
230
currentlySendingBatch = true
231
232
let url = URL(string: baseURL + "beacon_batch")!
233
var request = URLRequest(url: url)
234
request.httpMethod = "POST"
235
236
var items: [String: Any] = [:]
237
items["snippet_ver"] = SNIPPET_VERSION.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
238
items["pub_id"] = amPubID.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
239
items["os"] = "ios".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
240
items["os_ver"] = UIDevice.current.systemVersion.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
241
items["model"] = deviceModel.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
242
if vendorId != nil {
243
items["vendor_id"] = vendorId!.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
244
}
245
if locale != nil {
246
items["user_locale"] = locale!.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
247
}
248
if !gdprUser {
249
if adId != nil {
250
items["ad_id"] = adId!.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
251
}
252
if deviceAgent != nil {
253
items["agent"] = deviceAgent!.addingPercentEncoding(withAllowedCharacters: [])
254
}
255
}
256
do {
257
let bytes = try JSONSerialization.data(withJSONObject: batch, options: [])
258
items["batch"] = String(decoding: bytes, as: UTF8.self)
259
} catch {
260
items["batch"] = "[]"
261
}
262
263
let postString = getPostString(params: items)
264
request.httpBody = postString.data(using: .utf8)
265
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
266
let task = defaultSession.dataTask(with: request) { data, response, error in
267
if (error == nil) {
268
self.clearBeaconBatch()
269
self.markBeaconBatchSent()
270
}
271
self.currentlySendingBatch = false
272
}
273
task.resume()
274
}
275
276
private func sendUserInit() {
277
var items = [URLQueryItem]()
278
items.append(URLQueryItem(name: "snippet_ver", value: SNIPPET_VERSION))
279
items.append(URLQueryItem(name: "pub_id", value: amPubID))
280
items.append(URLQueryItem(name: "os", value: "ios"))
281
items.append(URLQueryItem(name: "os_ver", value: UIDevice.current.systemVersion))
282
items.append(URLQueryItem(name: "model", value: deviceModel))
283
if vendorId != nil {
284
items.append(URLQueryItem(name: "vendor_id", value: vendorId))
285
}
286
if locale != nil {
287
items.append(URLQueryItem(name: "locale", value: locale))
288
}
289
if !gdprUser && adId != nil {
290
items.append(URLQueryItem(name: "ad_id", value: adId))
291
}
292
293
var permission : String?
294
if CLLocationManager.locationServicesEnabled() {
295
switch CLLocationManager.authorizationStatus() {
296
case .notDetermined:
297
permission = "not_determined"
298
case .restricted:
299
permission = "restricted"
300
case .denied:
301
permission = "denied"
302
case .authorizedAlways:
303
permission = "authorized"
304
case .authorizedWhenInUse:
305
permission = "when_in_use"
306
}
307
} else {
308
permission = "disabled_globally"
309
}
310
311
if permission != nil {
312
items.append(URLQueryItem(name: "loc_permission", value: permission))
313
}
314
315
var urlComponents = URLComponents(string: baseURL + "user_init")!
316
urlComponents.queryItems = items
317
let url = urlComponents.url!
318
let task = defaultSession.dataTask(with: url)
319
task.resume()
320
}
321
322
// MARK: - Utility Macros
323
324
private func beaconId(_ b: CLBeacon) -> String {
325
let id = b.proximityUUID.uuidString + "." + b.major.stringValue + "." + b.minor.stringValue
326
return id.lowercased()
327
}
328
}
Copied!

Implement the call to startService

In your UIApplicationDelegate implementation, call startService on AreaMetrics from didFinishLaunchingWithOptions. Replace "PUBLISHER_ID" in the following example with your publisher ID:
Swift
Objective-C
1
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
2
AreaMetrics.sharedInstance.startService(pubID: "PUBLISHER_ID")
3
return true
4
}
Copied!
1
#import "AreaMetrics-Swift.h"
2
3
@interface AppDelegate ()
4
@end
5
6
@implementation AppDelegate
7
8
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
9
[[AreaMetricsSDK sharedInstance] startService:"PUBLISHER_ID_HERE"];
10
return YES;
11
}
Copied!
When launching your app, you should see printed in the debug console, "AreaMetrics Initialized Successfully!"

Ask the user for Location Permission

If your app is not already asking users for Location Always Allow permission, you will need to implement the following:
Last modified 2yr ago