Day 75 - 100 Days of Swift
Project 22 (part 1)
Day 75 is the first part of the twenty-second project. It is a project where you set up an app to detect nearby iBeacons and give the user feedback on how far away it is.
First, you add a few strings to Info.plist
to let the user know what you’ll be using their location for:
1
2
3
4
<key>NSLocationWhenInUseUsageDescription</key>
<string>$(PRODUCT_NAME) needs your location to help you find the nearest beacon.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>$(PRODUCT_NAME) needs your location to help you find the nearest beacon.</string>
The “always” permission would let the system wake up your app when it detects a beacon, even if it isn’t running, but it requires a lot more trust from the user. The “when in use” permission allows you to detect beacons when the app is running, but you need permission strings for both.
Then you lay out the storyboard with a big label in the middle where we’ll let the user know how far away the beacon is:

Then, in ViewController.swift
you import CoreLocation
, adopt the delegate
protocol for CLLocationManager
, and ask the user for location permission:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
var locationManager: CLLocationManager?
@IBOutlet var distanceLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.requestAlwaysAuthorization()
view.backgroundColor = .gray
}
}
Then, when the authorization status changes we’ll be notified via a delegate method:
1
2
3
4
5
6
7
8
9
10
func locationManager(_ manager: CLLocationManager,
didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedAlways {
if CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self) {
if CLLocationManager.isRangingAvailable() {
startScanning()
}
}
}
}
And there, if we have all the necessary permissions and access, we call startScanning
which looks like this:
1
2
3
4
5
6
7
8
9
10
private func startScanning() {
let uuid = UUID(uuidString: "5A4BCFCE-174E-4BAC-A814-092E77F6B7E5")!
let beaconRegion = CLBeaconRegion(proximityUUID: uuid,
major: 123,
minor: 456,
identifier: "MyBeacon")
locationManager?.startMonitoring(for: beaconRegion)
locationManager?.startRangingBeacons(in: beaconRegion)
}
Here, we’re hardcoding a UUID
, as well as major
and minor
identifiers just to make sure we’re only detecting the test beacon we set up with a second iOS device via the Locate Beacon app, and then telling the locationManager
to monitor and range for it.
Now, assuming we have permission, we’re receiving information about the beacon, but we’re not doing anything with it. So next we add another helper method which will update our UI:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private func update(distance: CLProximity) {
UIView.animate(withDuration: 0.5) {
switch distance {
case .far:
self.view.backgroundColor = .blue
self.distanceLabel.text = "FAR"
case .near:
self.view.backgroundColor = .orange
self.distanceLabel.text = "NEAR"
case .immediate:
self.view.backgroundColor = .red
self.distanceLabel.text = "RIGHT HERE"
default:
self.view.backgroundColor = .gray
self.distanceLabel.text = "UNKNOWN"
}
}
}
And then call it from another delegate method where receive updates about the beacons we’re ranging for:
1
2
3
4
5
6
7
8
9
func locationManager(_ manager: CLLocationManager,
didRangeBeacons beacons: [CLBeacon],
in region: CLBeaconRegion) {
if let beacon = beacons.first {
update(distance: beacon.proximity)
} else {
update(distance: .unknown)
}
}
Finally, I just made a few tweaks to make the UI look a little nicer:
1
2
3
4
5
6
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
// In viewDidLoad
distanceLabel.textColor = .white
With that, the app works. It is pretty magical that some simple little code like this can update the UI on your phone to reflect how far you are away from an iBeacon. As I walk around my house with my phone in my hand and the iPad sitting in a corner, my phone almost immediate updates to reflect the distance. It looks like this:

You can find my version of this project at the end of day 75 on Github here.