Using Kontakt SDK for iBeacons with Swift

Recently I started using iBeacon/Eddytstone devices to add some location based feature in mobile application for a store. I am using beacons from Kontakt.io I am not endorsing that this hardware at the moment because application is in prototype state at the moment and hardware evaluation is underway. In this post I will share some observations and issues that I came across during initial development using Kontakt SDK for iOS. I will also post sample code that shows how to write a barebone delegate to monitor beacon and bluetooth devices using SWIFT.

Getting started with using KontaktSDK with Swift

Kontakt has provided a complete section in their documentation about how to use the SDK with Swift. I am going to add some additional information that is missing from the documentation or is not clear. I am going to assume that you already have a iOS project that is using Swift or have created boilerplate single view application project.

Get Authorization To Use Location Service

Apple has very clearly stated in their documentation that post iOS8 application must obtain user's authorization to use location services. Following is from iOS developer documentation.

To configure and use a CLLocationManager object to deliver events, Always request authorization to use location services and check to see whether the desired services are available as described in Requesting Permission to Use Location Services.

To accomplish this, first you are going to have "NSLocationAlwaysUsageDescription" and/or "NSLocationWhenInUseUsageDescription" entries in your "plist" file of your application. These are string values used to display when raising authorization alert to user asking for permission. You can describe the intent and use of location services for your application to better inform the user about why they need to authorize use of location services. If you fail to specify these entries in "plist" file, your delegate will not get any beacon range notifications. The code will not raise any errors when you start use of location updates. So it can be sometime frustrating that you do not know why your application is not getting any notifications.

Disable "BitCode" In Project Setting

Current version of Kontakt SDK is not built with "Bitcode" enabled flag. When you will try to run your application that uses the API, you will see run time error like below.

    libkontakt-ios-sdk.a(KTKLocationManager.o)' does not contain bitcode. You must rebuild it
    with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor,
    or disable bitcode for this target. for architecture arm64
    clang: error: linker command failed with exit code 1 (use -v to see invocation)

If you want to use current version of Kontakt SDK, then you are going to have to disable "Bitcode" in your project settings. At some point, hopefully Kontakt will release a version of SDK that has "Bitcode" enabled. Till then, there is no alternative.

Missing i386 Architecture For Simulator

In XCode if you choose deployment target as iPhone5 or lower then you will be hit compile errors complaining about missing symbols for i386 architecture. Only thing you can really do it use simulator for iPhone6 and higher. Better yet, don't bother running your application in simulator because BLE capability does not work in simulator. Your best bet is to deploy it your actual device.

Implement KTKLocationManagerDelegate

Now you have appropriate project settings in place, it is time to implement a class that conforms to KTKLocationManagerDelegate from SDK. This is something along the line of implementing a delegate that conforms to CLLocationManagerDelegate for generic location services. The sample published in documentation talks about implementing the methods of this delegate in view controller itself. Well that is well and good just for sample. In real application you want to refactor this into a separate object that manages all location related implementation. Following code shows how I created a separate class for it.

public class BeaconsManager:NSObject, KTKLocationManagerDelegate{
    static let sharedInstance:BeaconsManager = BeaconsManager()
    var started:Bool = false
    var initialized:Bool = false
    let locationManager:KTKLocationManager = KTKLocationManager()

    private override init() {
        super.init()
        if (KTKLocationManager.canMonitorBeacons()){
            //TODO: Get list of beacons to monitors.
            // Generate regions that you would want to monitor.
            self.locationManager.setRegions(getBeaconRegionsToMonitor())
            self.locationManager.delegate = self;
            self.initialized = true
            NSLog("Location manager initialized!")
        }
        else{
            NSLog("Device not capable of monitoring beacons!")
        }
    }
    public func start()->Bool{
        if !initialized{
            NSLog("Location manager has not been initialized!")
            return false
        }
        locationManager.startMonitoringBeacons()
        started = true
        NSLog("Location manager started!")
        return true
    }
    public func stop(){
        if started{
            locationManager.stopMonitoringBeacons()
            started = false
            NSLog("Location manager stopped!")
        }
    }
    public func locationManager(locationManager: KTKLocationManager!, 
        didChangeState state: KTKLocationManagerState, withError error: NSError!) {
        if let err = error
        {
            NSLog("Something went wrong with Location Services: %@", err.description);
            if (error.domain == kCLErrorDomain){
                if let clError = CLError(rawValue: error.code){
                    switch clError{
                    case .Denied:
                        print("Location service access denied")
                    case .LocationUnknown:
                        print("Unknown location at the moment")
                    case .DeferredAccuracyTooLow:
                        print ("Location accuracy is low")
                    case .DeferredCanceled:
                        print("Deffered cancelled")
                    case .DeferredDistanceFiltered:
                        print("Location service: DeferrdDistanceFiltered")
                    case .DeferredFailed:
                        print("Location service: Deferred failed")
                    case .DeferredNotUpdatingLocation:
                        print("Location service: Deferred not updating location")
                    case .GeocodeCanceled:
                        print("Location service: geocode canceled")
                    case .GeocodeFoundNoResult:
                        print("Location service: Geo code found no location")
                    case .GeocodeFoundPartialResult:
                        print("Location service: Geo code found partial result")
                    case .HeadingFailure:
                        print("Location service: Heading failure")
                    case .Network:
                        print("Location service: Network error")
                    case .RangingFailure:
                        print("Location service: Ranging failure")
                    case .RangingUnavailable:
                        print("Location service: Ranging unavailable")
                    case .RegionMonitoringDenied:
                        print("Location service: Region monitor denied")
                    case .RegionMonitoringFailure:
                        print("Location service: Region monitor failure")
                    case .RegionMonitoringResponseDelayed:
                        print("Location service: Region monitor response delayed")
                    case .RegionMonitoringSetupDelayed:
                        print("Location service: Region monitoring set up delayed")
                    }
                }
            }
        }
    }

    public func locationManager(locationManager: KTKLocationManager!, 
        didEnterRegion region: KTKRegion!) {
        NSLog("Enter region %@", region.uuid);
    }

    public func locationManager(locationManager: KTKLocationManager!, 
        didExitRegion region: KTKRegion!) {
        NSLog("Exit region %@", region.uuid);
    }

    public func locationManager(locationManager: KTKLocationManager!, 
        didRangeBeacons beacons: [AnyObject]!) {
        NSLog("There are %lu beacons in range", beacons.count);
        for beacon in beacons {
            let clBeacon = beacon as! CLBeacon
            NSLog("%@", clBeacon.description)
        }
    }

    private func getBeaconRegionsToMonitor()->[KTKRegion]{
        var regions = [KTKRegion]()
        let venue1 = KTKRegion(UUID: "f7826da6-4fa2-4e98-8024-xxxxxxxxxxxx", 
            major: xxxxxx, andMinor: xxxxxx)
        regions.append(venue1)
        let venue2 = KTKRegion(UUID: "f7826da6-4fa2-4e98-8024-xxxxxxxxxxxx", 
            major: xxxxxx, andMinor: xxxxxx)
        regions.append(venue2)
        let venue3 = KTKRegion(UUID: "f7826da6-4fa2-4e98-8024-xxxxxxxxxxxx", 
            major: 49277, andMinor: xxxxxx)
        regions.append(venue3)
        return regions
    }
}

In this sample I have hardcoded beacon list. In actual application, you can get this list from Kontakt server using their REST API. In subsequent posts I will show how this will be done.

Following is log output when this little sample is run on iPhone.

    2015-12-14 01:46:51.083 LocationWatcher[522:223691] Location manager initialized!
    2015-12-14 01:46:51.096 LocationWatcher[522:223691] Location manager started!
    2015-12-14 01:46:51.124 LocationWatcher[522:223691] Something went wrong with Location Services: 
        Error Domain=kCLErrorDomain Code=5 "(null)"
        Location service: Region monitor failure
    2015-12-14 01:46:51.161 LocationWatcher[522:223691] There are 0 beacons in range
    2015-12-14 01:46:51.161 LocationWatcher[522:223691] There are 0 beacons in range
    2015-12-14 01:46:51.199 LocationWatcher[522:223691] There are 1 beacons in range
    2015-12-14 01:46:51.199 LocationWatcher[522:223691] 
        CLBeacon (uuid:F7826DA6-4FA2-4E98-8024-xxxxxxxxxxxx, 
        major:xxxxxx, minor:xxxxxx, proximity:2 +/- 0.32m, rssi:-68)
    2015-12-14 01:46:51.200 LocationWatcher[522:223691] There are 0 beacons in range
    2015-12-14 01:46:51.200 LocationWatcher[522:223691] There are 0 beacons in range
    2015-12-14 01:46:51.263 LocationWatcher[522:223691] There are 1 beacons in range
    2015-12-14 01:46:51.264 LocationWatcher[522:223691] 
        CLBeacon (uuid:F7826DA6-4FA2-4E98-8024-xxxxxxxxxxxx, 
        major:xxxxxx, minor:xxxxxx, proximity:2 +/- 0.32m, rssi:-68)
    

Now that basic delegate is set up, we can start adding business logic into delegate functions to do what we want when location updates are received.

comments powered by Disqus

Blog Tags