Putting barcodes in context - Barcodes with iOS: Bringing together the digital and physical worlds (2015)

Barcodes with iOS: Bringing together the digital and physical worlds (2015)

Chapter 7. Putting barcodes in context

This chapter covers

· Taking advantage of your user’s context to create “magic”

· Geofencing locations with Core Location

· Determining semantic location with iBeacons

Modern smartphones are uniquely aware of the current context a user is in. Whether you’re moving or standing still, or whether you’re inside a moving car—it feels totally natural to be able to open the Maps app and see your current position on the planet indicated by a pulsating blue dot.

Apple has an uncanny ability to pick the most beneficial technological advancements to be added in each new iOS version, for users as well as developers. The Core Location framework has been part of the iPhone operating system since the first version, but it was originally limited to getting user location by means of WiFi and cell tower triangulation. A three-axis accelerometer was also on board to allow the device to detect its own orientation so that it could rotate the UI to match.

On the second-generation iPhone 3G, Apple added a GPS receiver that further increased the accuracy of Core Location. But because Core Location beautifully abstracts determining the device location, there were almost no changes to the public APIs. The same can be said about the third-generation iPhone 3GS, which added a magnetometer allowing iOS to rotate maps to face the direction the user is looking.

Another generation later, with the iPhone 4, Apple added a three-axis digital gyroscope sensor, providing additional accuracy when detecting rotation. Are you beginning to see the trend?

As more sensor chips are added to the device, it knows more about the user and about the surroundings the user is in. This is commonly referred to as the user context. On your Mac, a context menu is a popup menu showing only activities that make sense in the context you point the arrow at. On mobile devices, apps can (and should) provide similar functionality and only present users with information or actions that are relevant to the context they’re presently in.

This chapter introduces the two major functionalities found in Core Location: geographic context from the user’s geolocation and semantic context from tracking and ranging iBeacons. The difference between these two will soon become clear as we look at how you can use these to better serve your customers.

7.1. Understanding multiple layers of context

Let’s assume for a moment that you’re inside an Apple Store. Your iPhone can infer this from your geographic location, because you’re within a certain radius of the store. Your iPhone is strongly receiving the signal from an iBeacon, which indicates that you must be standing close to the table displaying iPads.

I’d like to point out two further levels of context that are provided by barcodes. First, the Apple Store app lets you scan barcodes found on various products, such as an accessory cable. This indicates that you have a specific interest in such a product, which is another piece of context. In the case of Apple, you can pay for the cable with your iTunes account and walk out of the store without ever having to talk to a genius. This is a great feature to offer to customers who are either shy or in a hurry.

Most electronic products have a second barcode—besides the GTIN product code—showing the device’s serial number. If you scan a MacBook’s GTIN, your context would be “any such model of MacBook.” But if you scanned the serial number on the MacBook’s box, your context would be a single unique device. This specific device has unique properties, like tech specs or when AppleCare runs out. You could build an inventory-tracking app that maintains a database of all the serial numbers of your company’s devices. Then you could assign specific pieces of equipment to individual employees, and track who in your company is using what.

In the sample app for this chapter, we’ll look at three of the four levels of context (depicted in figure 7.1), because both barcode-related contexts work the same way, technically speaking. Besides, several previous chapters have already taught you how to add a modal view controller with a barcode scanner to your apps.

Figure 7.1. Multiple layers of context

As British writer Arthur C. Clarke put it, “Any sufficiently advanced technology is indistinguishable from magic.” (Profiles of the Future, 1962) The situational awareness emerging from the iPhone’s built-in sensors is the main ingredient you can use to delight your users and create “magic” in your apps. You might not be able to anticipate all your user’s needs, but anticipating a few more than your competitor’s apps might make a big difference to your bottom line.

7.2. Building a YardSale app

Until now, people selling off things they don’t need anymore have done so the “traditional way”—yard sales, garage sales, and the like. Those one-person flea markets often have a treasure trove of goodies, but interested buyers might never know about them if they don’t happen to pass by the home of the seller at the right time. There has to be an app for that.

Let’s assume that you’re working for a startup that aims to establish “yard sales as a service” to conquer this untapped market. People having a yard sale would sign up with your company as sellers, submit the dates they’re holding a sale, and enter a list of products and the prices they’re offering those items at. A free app, which you’re tasked to develop, should be put on the App Store to help drive prospective customers to those sellers.

Your company could sell packs of iBeacon devices to your users or guide them to one of the many companies that sell iBeacons. Any beacon adhering to Apple’s iBeacon standard can be used for this purpose, including virtual (software) iBeacons. Apps running on devices capable of Bluetooth LE can emit an iBeacon. We’ll use this software-based option for testing the sample app so that you don’t have to rush out to buy a hardware iBeacon.

The finished YardSale app will have these features:

· The app will feature a list of currently active yard sale locations and show them on a map.

· If a user taps on one of the location pins, they’ll see the sale’s name in a callout bubble.

· Tapping on the callout accessory will show the in-store UI for this yard sale.

· The user can easily “leave” the store by tapping a Close button.

· If the app is inactive and the user walks into the vicinity of a yard sale, they will get a notification about the sale.

· When the app is active or becomes active because of the user tapping on the notification action, the in-store UI will also be shown.

· The in-store UI shows a list of products grouped by the table they’re on (to enable multitable yard sales).

· If an iBeacon is received more strongly than any others, only the table associated with this beacon is shown.

· The in-store UI displays a button allowing the user to scan a barcode on an item for sale, and after scanning an alert will show the layers of context.

In contrast to our previous sample apps, this one doesn’t just have just one interface, but two: one for outside of stores and one for inside, as shown in figure 7.2. Plus, there will be notifications outside of the app, which could be counted as a sort of minimalistic UI for your app. The purpose of the YardSale app is to demonstrate how you can take advantage of the user’s context to decide which of these user interfaces will most benefit the user.

Figure 7.2. The finished YardSale app

7.2.1. Creating the outside-the-store experience

No matter how many physical store locations you have, there will always be a “whole world” outside of the stores. The user interface you’ll develop in this section will aim to provide information to your users that’s beneficial when they’re out and about. At its simplest, the UI will show a map with pins showing the locations of yard sales. This will allow the user to look up the closest sale locations.

Let’s get started building this part of the YardSale app.

Create a new app project using the Single View Application template, and call it “YardSale.” Using the refactor tool, rename the ViewController class with a more descriptive name: MapViewController. This will be both the root view controller of your application and the controller for the map.

For showing the map and pins, you’ll use MKMapView provided by the MapKit framework. In the main storyboard, add a new map view as a subview of the previous root view that the Xcode template set up for you. Add constraints to pin the sides of the subview flush with the sides of its superview. The top should be aligned with the top layout guide so that the map doesn’t extend under the status bar. This way the map will always resize correctly if you rotate the device.

For setting the annotations on the map, you need an outlet. Open MapViewController.h in the assistant editor and Ctrl-drag the map view onto it to create an IBOutlet, as shown in figure 7.3.

Figure 7.3. Adding an outlet for the map view

In order to reference methods and classes from the MapKit framework, add it to your target’s Link Binary With Libraries build phase. I recommend that you add the main headers for Apple frameworks to the precompiled header file (PCH). This speeds up compilation and makes all symbols available for all your source files. For MapKit.framework the header to add is MapKit.h. You can look at the YardSale sample app contained in this book’s sample code to see this.

For this demo, the locations will come from a property list that has the following four values for each sale:

· The geolocation of the yard sale, coded as latitude and longitude

· The name of the yard sale

· A unique identifier to identify each yard sale

In the plist, those yard sale locations are represented as dictionaries, but inside the app you’ll want to convert them into proper objects of type SalePlacemark. By deriving from MKPlacemark, you can directly use the SalePlacemarks as map view annotations.

The SalePlace class extracts these values from the surrounding dictionary and puts them into the appropriate instance variables. Because MKPlacemark already has a property to take on the location, you can pass it through to the dedicated initializer:

You’re using the existing placemark subclasses in the API. CLPlacemark in Core Location is a place with a radius and an address dictionary. MKPlacemark—found in the MapKit framework—is a subclass of CLPlacemark, adding the methods of the MKAnnotation protocol. This means you can use those placemarks as model objects for adding annotations to a map view.

In the YardSale app, the handling of the plist is encapsulated in the YardSaleManager class. A private helper method loads the list of sale locations from the plist, converting them to SalePlace instances:

It’s very tempting—especially in a small app like this one—to place such model-related code in the view controller where you’re using it. But as you’ll see, you’ll also need to access those placemarks from the app delegate, so it’s smarter to have a dedicated model controller class likeYardSaleManager. Once it’s instantiated, you can pass a reference to it to all objects needing to interact with the sales places.

This instantiation of the yard sale manager instance is done in the first method that’s called when your app launches. The storyboard has no reference to the app delegate, which would allow you to set an outlet on the MapViewController. But because you know that the map view controller will be set as the rootViewController of the app’s window, you can get the reference from there:

You can now place your pins on the map, ideally right before the MapViewController’s view will become visible. But you can’t put it in viewWillAppear:, because this method will also be called if the in-store UI is being dismissed. In real life you’d need further logic to prevent double-setting the same annotations and to handle updating. But for this sample app we’re foregoing such complexities:

With this code in place, your app should load the placemarks from the app bundle and show them as red pins on the map.

If you tap on one of the red pins, a callout bubble is shown. Its contents stem from the values of the title and subtitle properties returned from SalePlace:

- (NSString *)title {

return [NSString stringWithFormat:@"%@'s Yard Sale", _name];

}

- (NSString *)subtitle {

return @"Cool Offers";

}

You also want to be able to enter a sale location via an accessory button in the form of an info button on the right side of the callout bubble (see figure 7.4). Tapping this button should show the in-store UI with information and actions specific to this particular site.

Figure 7.4. Apple’s yard sale

Getting your home coordinates

Please replace the “Oliver” sale place and coordinates in Locations.plist with your own location. For your testing, you can also add a few places near your current location. The locations in the YardSale app’s property list are almost certainly too far away from you for reasonable testing.

A quick way to find an address’s geographic coordinates is to search for them on Google Maps. Note the decimal numbers in the address bar—those are the latitude and longitude degrees of the address. Copy them to the latitude and longitude fields in the property list.

Getting coordinates with Google Maps

Another way to find geographic coordinates, which is slightly more involved but also more suitable for iOS developers, is to run the GetLocation sample app, which you can find with the book’s source code. This displays your current geolocation together with a reverse-geocoded address for your present location.

Make the MapViewController the delegate of the map view by connecting the delegate outlet with the view controller in Interface Builder. The following MKMapViewDelegate method lets you customize the annotation pins:

If you implement this delegate method, it’s important to exclude the annotation representing the user’s current location. Otherwise it would also get a red pin, confusing users who are expecting to seeing a blue pulsating dot for their current location.

Finally, to trigger the segue to the in-store UI, you need to implement another delegate method that’s called as soon as the user taps on the info button for a callout:

The aforementioned segue doesn’t exist yet. You’ll implement the in-store view controller next.

7.2.2. Implementing the in-store user interface

You want to show users information relevant to yard sales they’re visiting, whether virtually, when they tap on the callout, or physically, when they come close to the place of the sale. For this purpose, you’ll implement a table view controller that shows in-store information.

The transition from map to in-store UI could either be a modal segue or push segue. I chose the modal option here because then the map remains underneath the sliding-in view controller. This gives the user a clue that in order to return to the map, they need to “exit” the store.

Add a new UITableViewController subclass to the project and call it InStoreViewController. In Interface Builder, add this new view controller embedded in a navigation controller. This will give you a navigation bar on which to mount the Close button (see figure 7.5).

Figure 7.5. Adding the in-store UI

There’s no UI element in the MapViewController to which you could connect a segue, so instead you can connect it to the Manual option under Triggered Segues, as shown in figure 7.6. Doing so creates a new segue that you set to the “modal” segue type. To be able to call it from your code, give it an identifier of ShowSalePlace.

Figure 7.6. Adding the in-store UI segue

Add a dummy unwinding method to the MapViewController. This lets you connect the Close bar button to the green Exit icon. (Please refer to section 6.1.4 for an explanation of unwind segues.)

You’ve now created a segue that you can call programmatically via its identifier, and you’ve also set it up so that the modal view controller is dismissed when the Close button is tapped. In fact, you’re already performing the segue in reaction to the callout bubble’s accessory button being tapped.

You need to pass the relevant SalePlace to the InStoreViewController. This is done in prepareForSegue:sender:, which is called right before the segue animation happens. The in-store view controller has a salePlace property to receive it:

You’re passing the MKAnnotationView as the sender of the segue when calling it from code (see the code snippet at the end of section 7.2.1). The annotation view object has an annotation property that references the SalePlace object belonging to it. Sometimes you see developers “abusing” the sender method to directly pass the model object, but I frown on this practice because in UIKit nomenclature the sender parameter is supposed to be the control triggering some activity, not a model object.

To demonstrate that you’re showing the correct yard sale with your in-store view controller, you can set the title in viewWillAppear: to the SalePlace title:

self.navigationItem.title = self.salePlace.title;

This completes the UI work for both views that the YardSale app will provide. The app launches in “global view” showing the map of pins. If you tap on a pin, a callout shows more details and an info button. Tapping on this shows the in-store UI modally. Tapping on the Close button dismisses it.

The next step is to show the in-store UI if the user gets close to a yard sale.

7.3. Geofencing store locations

You want your app to alert users when they come close to a listed yard sale, even if the YardSale app isn’t active. Apple’s rule is that apps may not run in the background and use up the battery. There are exceptions to this rule that the developer can request for specific use cases. Receiving background location updates is one of them.

Fortunately, you don’t need to manually monitor the current location to compare it with yard sale locations. In iOS 4, Apple introduced region monitoring, also often referred to as geofencing. You can register up to 20 circular regions, and iOS will wake your app to inform it if one of those regions has been entered or exited. iOS does this tracking at a fraction of the energy cost it would take if you were to do it manually.

7.3.1. Introducing region monitoring

A CLCircularRegion, used for monitoring, is defined by a geographic center (latitude/ longitude) and a radius (in meters) around those coordinates. To monitor for one such region, you create a CLLocationManager, set a delegate to receive updates, and start monitoring for it:

It might take a while for iOS to determine the status of this region. But when it does, your delegate object receives a call to locationManager:didDetermineState:forRegion:. This state can be CLRegionStateUnknown, CLRegionStateInside, or CLRegionStateOutside. You can coerce the location manager to update the region state right away with the requestStateForRegion: method.

7.3.2. Monitoring an unlimited number of regions

In our yard sale scenario, you want to have thousands of sellers as clients. How can you work around Apple’s limit of only monitoring 20 regions with a single app?

The trick is to only register for monitoring the 10 yard sale locations closest to the current location of the user. If the user moves by a certain distance, you can update the registrations. This achieves the same effect as if you were monitoring all yard sales at the same time, albeit much more efficiently.

To determine the 10 candidate regions, you need to sort the locations by distance from a given point, and then only return the 10 closest ones. YardSaleManager gets a new method for this purpose:

The next several code pieces in this section are from the _updateMonitoredRegions-ForLocation: method, which you can inspect in AppDelegate.m in the YardSale sample code.

The following helper method receives the current user’s location and then updates the monitored regions according to plan. First, you check if region monitoring is supported at all:

if (![CLLocationManager isMonitoringAvailableForClass:

[CLCircularRegion class]]) {

NSLog(@"Monitoring not available for CLCircularRegion");

return;

}

Then you retrieve the 10 closest yard sale places and get a list of their identifiers. Regions you’re still interested in aren’t touched because they’re already being monitored. Monitoring is stopped for regions that are now too far away from the user:

After this loop, you have a list of region IDs that you still need to start monitoring. You also need to determine the maximum distance that one of these regions is away from the user’s location:

Knowing the maximum distance that a monitored region is away from the user allows you to defer location updates until the user has traveled a significant distance. This allows iOS to refrain from waking up your app for each minor movement. Half the distance between the user and the farthest away monitored region is a good compromise:

[_locationMgr

allowDeferredLocationUpdatesUntilTraveled:maxDistance/2.0

timeout:CLTimeIntervalMax];

You can specify a distance or a time interval or both for the preceding method. The CLLocationDistanceMax and CLTimeIntervalMax constants let you specify that you don’t want to limit this dimension.

You don’t want to wait too long before knowing the monitored regions’ state, so you want to trigger a state update right after updating the regions to be monitored. As of iOS 7.1, there’s an issue with requesting a state update too soon after removing monitored regions—the monitoring would fail altogether. A workaround for this issue is to wait for at least 0.1 seconds. The following code waits for 0.2 seconds just to be safe:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW,

(int64_t)(0.2 * NSEC_PER_SEC)),

dispatch_get_main_queue(), ^{

for (CLRegion *oneRegion in

_locationMgr.monitoredRegions) {

[_locationMgr requestStateForRegion:oneRegion];

}

});

You now have a convenient method for updating the monitored regions. The next thing to do is call it if there’s been movement by the user.

7.3.3. Updating monitored regions based on user location

Like other sensors, users need to authorize access to their location information. Up until iOS 7, the first time an app requested location updates, a dialog would ask the user for permission. As of iOS 8, the app has to explicitly request authorization, and Apple split the permission into two parts:

· Only when the app is active and showing its UI, dubbed “when in use”

· “Always,” even when the app is in the background

This gives users the cozy feeling that they’re in control of which apps get which level of access. For example, a POI search app would only ever require when-in-use authorization.

The user might deny this access, or location services might be disabled through settings or by company policy. You can add the following helper method to deal with those possible authorization scenarios—it’s called right after the app launches:

If you encounter a denied or restricted authorization status, you should tell the user that you can’t provide app functionality dependent on location. For the denied status, the user can simply enable location services for your app in the Privacy section of the Settings app. For the restricted status, the user can’t do anything but complain to the IT department (or parents) who made this policy decision.

Compiling with iOS 7 and iOS 8 SDKs

This code compiles with both the iOS 7 and iOS 8 SDKs because the __IPHONE_OS _VERSION_MAX_ALLOWED precompiler macro hides all code that the earlier SDK would complain about. This technique is particularly useful if you want to support new SDK features without breaking the build for your colleagues who might still use an older Xcode version.

Apps that were built with the iOS 8 SDK can still execute on iOS 7 devices if the deployment target build setting permits it. In order to prevent an “unrecognized selector” crash from calling a nonexistent method, you should ask if the object respondsToSelector:.

Your app requires access to the user’s location while the app is not active in order to update the monitored regions, so you can tell the user that if you only have when-in-use authorization. The helper methods showing the respective UIAlertViews aren’t shown here.

You get one chance to state your reason for needing location access when the authorization alert pops up (see figure 7.7). iOS 7 appends the contents of your NSLocationUsageDescription info.plist key to the dialog. You can also localize your message by putting it into the localized InfoPlist.strings files instead. iOS 8 adds two new such keys for the two kinds of authorization: NSLocationWhenInUse-UsageDescription and NSLocationAlwaysUsage-Description. For backward compatibility with iOS 7, all three strings should be present. The iOS 7 authorization is equivalent to the “always” authorization in iOS 8.

Figure 7.7. Location access dialog with custom reason

Monitoring significant location changes instead of normal location updates also reduces unnecessary battery drain. This works fine on physical devices, but I found that there are some problems with getting the iOS simulator to report those. As a workaround—while testing your app on Simulator—you can replace the startMonitoringSignificantLocationChanges line with the following to get a similar update profile:

_locationMgr.distanceFilter = 1000;

_locationMgr.desiredAccuracy = kCLLocationAccuracyKilometer;

[_locationMgr startUpdatingLocation];

Regardless of which kind of location updates you choose (normal, significant, or deferred), they always get reported to the same CLLocationManagerDelegate method:

A location might be reported multiple times, so you keep the most recent one stored in the _mostRecentLoc ivar and only update regions if a newly reported location is different.

In addition to updating the monitored regions when the location changes, you also want to update whenever the app becomes active. This occurs if it returns from being backgrounded and also after the user reacts to the authorization dialog. If there’s a most recent location stored in the ivar, you can also call the update method:

- (void)applicationDidBecomeActive:(UIApplication *)application {

[self _enableLocationUpdatesIfAuthorized];

if (_mostRecentLoc) {

[self _updateMonitoredRegionsForLocation:_mostRecentLoc];

}

}

If the user leaves the app via the home button and changes the authorization in the privacy settings, this delegate method is where you should react to this. This method is also called following the app’s launch. This is why you call _enableLocation-UpdatesIfAuthorized here. This enables location updates if you have sufficient authorization, or it outputs the appropriate warning alerts to tell the user that certain app features won’t work until “always” authorization is granted.

By monitoring significant location changes in combination with deferred updates, you keep battery drain to a minimum while keeping the list of monitored yard sale locations updated. This creates the effect of being able to monitor a virtually unlimited number of circular regions with only minimal battery drain.

7.3.4. Notifying users when entering a monitored region

The main purpose of this app is to alert users if they enter the vicinity of a yard sale, even when the YardSale app isn’t running. You can achieve this by sending a location notification as soon as a region’s state changes to “inside.” You need to be able to get the SalePlace to correspond to a given identifier, so YardSaleManager gets another helpful method:

- (SalePlace *)salePlaceForIdentifier:(NSString *)identifier {

NSPredicate *predicate =

[NSPredicate predicateWithFormat:@"identifier == %@", identifier];

NSArray *matches =

[[self annotations] filteredArrayUsingPredicate:predicate];

return [matches firstObject];

}

There should only ever be a single SalePlace with a given identifier. Because the method for filtering an array using a predicate returns an array, you return only the first object.

Before iOS 8, users couldn’t prevent local notifications from appearing. Some apps, particularly games, abused this free reign to annoy users. Thankfully, Apple is unifying the privacy settings for remote and local notifications. As of iOS 8, your app gets its own privacy section in Settings where the user can modify location authorization as well as notification settings.

But you still have to support iOS 7 devices, so some extra code is necessary to avoid crashing when calling methods defined in the iOS 8 SDK. The following helper method should be called right after app launch to let iOS know the kinds of notifications you plan to send:

The first time the app is launched on iOS 8, it takes a few seconds for the user to approve of your app sending notifications. The first-ever call to registerUserNotificationSettings: causes an authorization alert to pop up. You get a chance to react to the given authorization inside the following app delegate method:

This delay only occurs the first time. Subsequent calls to the registration method cause an immediate callback. So—on iOS 8—this is the best place to put all code requiring knowledge of the user’s authorization choices. In the YardSale app, you want to update the monitored regions’ state and notify the user of any nearby yard sales.

The next helper method constructs and sends a local notification. Those work and look much like remote push notifications. Depending on the user’s preference, they’re either shown as a top banner or an alert. The user can also opt to include or exclude them in the Notification Center.

Because of the notification unification in iOS 8, this helper method contains some code that modifies the local notification based on the current notification settings. Failure to do so causes the notification to be ignored by the system, and the resulting log message scolds you for it.

The parameter for specifying a delay is useful when testing the code for simulating a user triggering the notification action:

You’ll need the SalePlace identifier later on for identifying which yard sale the user wants to execute the action for, so you put it in the location notification’s userInfo dictionary. Instead of using the standard system sound, you can also specify the name of a CAF sound file contained in your app bundle. How about the sound of a cash register?

Apple provides instructions for how to convert sound files into a format appropriate for use as notification sounds.[1]

1 See “Registering, Scheduling, and Handling User Notifications” in the “Local and Remote Notification Programming Guide”: http://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/IPhoneOSClientImp.html.

In the following method, which gets called when the state of a monitored region is updated, you want to avoid sending the same notification multiple times. A new ivar, _lastNotifiedSaleID, keeps track of the last sale place you sent a location notification for. You’ll send the notification as soon as the region-monitoring callback method tells you about the boundary crossing:

To test this contraption, make sure that you have your current home location set up in the locations file. Set the duration to 5 seconds. This way you can launch the app and press the home button to send it to the background. When it sees that you’re already inside your home location, it will schedule a local notification 5 seconds in the future, as shown in figure 7.8. The notification style for this screenshot has been switched to alert style because the banner style doesn’t show the action button text.

Figure 7.8. Alert-style local notification

If the user taps on the Visit button in the alert-style notification or taps on the notification banner, iOS will return control to your app. If it was suspended in the background, then it gets awoken; if it was terminated, it gets relaunched. In both cases, a method on the application delegate is called, which gives you an opportunity to react to the user’s response.

You add a new method to the MapViewController so that you can trigger the showing of the in-store UI from the app delegate:

The preceding method simulates tapping on the annotation pin accessory button, allowing the prepareForSegue:sender: method to retrieve the annotation from the sender parameter and pass it to the in-store view controller. With this method you can trigger the “visit” from within the app delegate.

In the app delegate, the following helper method presents the in-store UI for a given store ID. This also makes sure that you’re not trying to present the modal view controller twice. A friendly message welcomes the user to the store:

Once you’ve scheduled the location notification, it will “fire” at the given time. What happens then depends on whether or not the app is currently active in the foreground and whether it’s running on an iOS 7 or 8 device (see table 7.1).

Table 7.1. Receiving a location notification

App state

iOS 7 (and earlier)

iOS 8

Foreground

Receive application: didReceive-LocalNotification: immediately

Receive application: didReceive-LocalNotification: immediately

Background

Receive application: didReceive-LocalNotification: after user taps on action and app becomes active

Receive application: handle-ActionWithIdentifier: for-LocalNotification: completion-Handler: after user taps on action and app becomes active

To cover all four scenarios, you need to implement the two application delegate methods, as follows:

If the app is already in the foreground while sending this local notification, no alert or banner is shown. Instead, iOS calls the application:didReceiveLocalNotification: delegate method right away. This means that if you’re entering a yard sale region while walking around with the map showing, the app will also move into the in-store UI. One might frown on such unexpected activity by the app, but it’s quite likely that users would rather see information about the yard sale they just stumbled on. For the rare case that a user still wants to peruse the map—to find other nearby sales—there’s the Close button.

You’ve now covered all scenarios inside and outside the app. Whether users react to a local notification or navigate to a yard sale by means of the map, they’ll still end up seeing the in-store UI.

7.4. Enhancing the in-store UI with iBeacons

The framework in charge of everything related to a user’s location is Core Location. Over the years, Apple has enhanced it and also made the technology backing it faster, more energy-efficient, and more accurate. The location on Earth is put together by triangulating cell towers and WiFi networks and—where the data can be received—with meter-accurate geopositioning from GPS and GLONASS satellites.

The dangers of using local notifications for UI flow

In the method that deals with monitored region state updates, you’re always sending a local notification regardless of application state. This allows you to specify a delay so that you can press the home button and see the notification arrive for testing. It also allows you to demonstrate various scenarios of your app reacting to local notifications.

In a production app, you should only send the local notification if the app is in the background. If a user of iOS 8 hasn’t authorized your app to send notifications, this would disable the functionality of showing the in-store UI while the app is in the foreground. To avoid this problem, you want to show the in-store UI without detouring via the local notification. The YardSale app features this modification.

But those technologies aren’t able to help users navigate inside a store or shopping mall. Without the aid of satellites, location accuracy is within hundreds of meters. For this scenario, some genius at Apple invented iBeacons.

7.4.1. Introducing the iBeacon system

Rather than trying to supplement the accuracy of geolocation, iBeacons provide a semantic context to your app. They piggyback on top of Bluetooth Low Energy (BTLE) advertisements. BTLE, also known as Bluetooth 4.0, has only its name in common with earlier Bluetooth versions. It’s a brand new standard aiming to be extremely power efficient. BTLE peripherals send out advertisement packets with identifiers telling interested listeners what kinds of services they offer. Those listening devices can then establish a connection to transfer bursts of data.

Ingeniously, iBeacon doesn’t need any connection to work, because all necessary information is transmitted inside the advertisement packet: a beacon UUID, and a minor and major value. Thanks to this trick, it’s extremely energy efficient to listen for iBeacon advertisements. For the same reason, you can get hardware iBeacons for around $30 each that run on a button battery for more than a year. All these need to do is send the iBeacon advertisement packet every couple of seconds. Very little energy is used for these transmissions because their range is only a couple of meters(see figure 7.9).

Figure 7.9. Two hardware iBeacons compared to iPhone

One interesting iBeacon usage scenario has to do with Passbook. Imagine a bus company sticking an iBeacon device next to the front doors of all their buses. If they add the beacon identifier as a relevancy criteria to their Passbook tickets, then iPhones can show the bus ticket on the lock screen when you get close to entering the bus. Passbook supports semantic and geographic locations as relevancy criteria.

For the YardSale app, you could use iBeacons to find out which table at a yard sale the user is closest to. That’s assuming that you want to support multitable yard sales or flea markets.

There are two styles of interaction with iBeacons:

· MonitoringThis is eerily similar to monitoring geolocations, which was demonstrated in the previous section. It uses the same methods—the only difference is how you construct the region to be monitored.

· RangingThis measures the signal strength being received from individual beacons to determine an approximate distance between them and the user.

7.4.2. iBeacon monitoring at a glance

For monitoring, instead of using a CLCircularRegion you use a CLBeaconRegion:

NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:

@"C70EEE03-8E77-4A57-B462-13CB0A3ED97E"];

CLBeaconRegion *beacon = [[CLBeaconRegion alloc]

initWithProximityUUID:uuid

identifier:@"YardSale Beacon"];

[_locationMgr startMonitoringForRegion:beacon];

If you build with the iOS 7 SDK, you’ll always be able to construct a beacon region, but that doesn’t mean that the current device is able to monitor for beacon regions. Use the isMonitoringAvailableForClass: class method of CLLocationManager to determine if monitoring is available for CLBeaconRegion.

There are three variants of the initializer method available, depending on how narrowly you want to specify the beacon to be monitored. The primary beacon identifier is a universally unique identifier (UUID). In addition, you have two 16-bit integer values—the major and minor values. For example, if all Apple Stores shared the same UUID, the major value could identify the store location and the minor value could identify a semantic location inside each store. The preceding code only monitors for beacons with a particular UUID, regardless of the major and minor values, but those other variants will let you restrict to a specific major value or a specify a major and minor combination.

Generating a UUID

If you ever need to generate a UUID for your own purposes, there’s a simple solution: in Terminal, just type uuid and press Enter. You’ll get a freshly generated UUID.

iBeacons aren’t monitored via the traditional Core Location technologies. Instead, they require that Bluetooth be active. If you try to monitor a beacon region with Bluetooth turned off, the user will get an alert from the system recommending that they turn on Bluetooth “for greater accuracy.”

The 20-region limitation of Core Location doesn’t differentiate between circular georegions and beacon regions. You could be monitoring 10 yard sale locations and 10 different beacon regions. For the purposes of the YardSale app, you have no use for beacon monitoring because you want to know about a nearby sale place, even when the user is 100 meters away. Bluetooth doesn’t reach that far. Instead of beacon monitoring, we’ll implement iBeacon ranging for the times when the user is already inside a yard sale area.

7.4.3. Making any app emit an iBeacon

Any app can act as an iBeacon while it’s in the foreground. Remember when you built a ticket-verifier app in chapter 4? If you’d made this app an iBeacon emitter and added the beacon’s UUID to the movie ticket passes, those tickets would have popped up on your guests’ lock screens as soon as they came close to the person at the door wanting to check their tickets.

I’m assuming that you haven’t bought a bunch of hardware beacons yet, so creating a software iBeacon is very useful when testing app scenarios involving iBeacon monitoring or ranging. The BeaconEmitter app included in this book’s source code contains an implementation for you to use.

Emitting a beacon is slightly complicated because Core Bluetooth requires separate authorization. Also, you should only try to trigger actions with Bluetooth powered on. You can see the relevant code in the BeaconEmitter app’s ViewController.m. The _startAdvertising method also constructs a CLBeaconRegion, but only for generating the peripheral data dictionary:

The beacon region object isn’t needed after calling peripheralDataWithMeasuredPower:. The power parameter on this method will allow you to specify a custom RSSI value, and setting it to nil uses a reasonable default. The peripheral data dictionary contains Apple’s iBeacon service ID, your UUID, the major and minor values, and the RSSI value. Nothing more to see there.

iOS will automatically pause these beacon advertisements a few seconds after the user sends the app into the background via the home button. Unfortunately, this can’t be changed, even by specifying any of the two background modes related to Bluetooth. Apple is rather stubborn when it comes to conserving power. Using such a software iBeacon requires the app to remain running in the foreground.

7.4.4. Determining distance to iBeacons with ranging

Geofencing via geographic coordinates reliably informs you about nearby yard sale locations. But when on the premises or inside the store, you want to show the information that’s relevant to semantic locations at this sale place. Imagine that your yard sale location has multiple tables or participants, and you put a hardware beacon on each table. Ranging is the process whereby iOS will report to you the relative signal strength at which iBeacons are currently being received. If you’re closer to one beacon than another, you’ll be receiving it at a greater signal strength.

Various kinds of hardware beacons might emit their signals at different strengths, so some allow you to calibrate by giving you a Received Signal Strength Indication (RSSI) value. This value—measured in decibels—is the strength at which a device would receive the signal at a given distance. But to keep things simple, let’s assume that all your software beacons are sending at the same strength.

The iPhone 4S and later versions, and iPad 3 and later, have Bluetooth 4.0 chipsets. Run the provided BeaconEmitter app on a few of your older iOS devices for testing iBeacon ranging. Leave the UUID and major values as they are, and modify the minor value to be the table number. Minor 1 would be table number 1; minor 2, table number 2; and so on.

There’s always the possibility that Bluetooth isn’t available because either the user’s device is too old or it’s disabled. The default in-store view shows five sections, one for each table. In these sections there will be 10 products each. If the _filteredTable ivar is -1, then you want to show all table view sections. Otherwise, you show only the corresponding section. This way, iBeacon causes the list of products to be filtered down to the ones that are currently relevant for the user’s semantic location.

The contents of the table view are intentionally very simple. Their sole goal is to show you that the data changes as you come close to different beacons:

The same technique shows section headers only when needed:

- (NSString *)tableView:(UITableView *)tableView

titleForHeaderInSection:(NSInteger)section {

if (_filteredTable == -1 || section == _filteredTable) {

return [NSString stringWithFormat:@"Table %ld", (long)section+1];

}

return nil;

}

The row cells only show a sequential product number and which section they belong to:

- (UITableViewCell *)tableView:(UITableView *)tableView

cellForRowAtIndexPath:(NSIndexPath *)indexPath {

UITableViewCell *cell = [[UITableViewCell alloc]

initWithStyle:UITableViewCellStyleDefault

reuseIdentifier:nil];

cell.textLabel.text =

[NSString stringWithFormat:@"Product %ld on table %ld",

(long)indexPath.row+1,

(long)indexPath.section+1];

return cell;

}

Now that we’ve got the boring part out of the way, you can implement a location manager to take care of beacon ranging. You can have as many CLLocationManager instances as you like, so it’s quite practical to have one dedicated to in-store beacon ranging that’s only activated whileInStoreViewController is showing:

Right before the view controller’s view appears, you create a dedicated location manager and start ranging, if available:

The preceding code starts the ranging for all iBeacons belonging to the given CLBeaconRegion. Let me stress again that how narrowly you define this region determines which beacons are considered to be part of it. For this example, all beacons sharing the UUID make up the in-store beacon region, regardless of major and minor values.

What goes up must come down. So you stop ranging and tear down the ranging manager if the view controller is going away:

A helper method groups the actions you need to do when the filter for your product table view changes:

- (void)setFilteredTable:(NSInteger)table {

if (table != _filteredTable) {

_filteredTable = table;

// refresh table sections with animation

NSIndexSet *indexSet =

[NSIndexSet indexSetWithIndexesInRange:

NSMakeRange(0, NUMBER_TABLES)];

[self.tableView reloadSections:indexSet

withRowAnimation:UITableViewRowAnimationAutomatic];

}

}

If there are any problems with ranging the beacons, a CLLocationManagerDelegate method is called. You want to deal with any errors gracefully and reset the filter on your table:

- (void)locationManager:(CLLocationManager *)manager

rangingBeaconsDidFailForRegion:(CLBeaconRegion *)region

withError:(NSError *)error {

NSLog(@"%@", [error localizedDescription]);

[self setFilteredTable:-1];

}

Core Bluetooth Hanging in iOS 7.1

On iOS 7.1 there are some circumstances that can cause Core Bluetooth to hang internally, effectively causing all ranging to fail. The only fix is to restart the device.

More often than not, ranging works perfectly and iOS calls another delegate method, passing it an array of CLBeacon objects. Those possess the raw RSSI value and also a CLProximity value specifying whether the distance is unknown, near, immediate, or far. This array also contains beacons that have recently disappeared with no distance info. Those you need to filter out. The remaining beacons get sorted by signal strength and the minor value is what you’ll filter the table by:

For testing the beacon ranging functionality, you can place two iOS devices with the BeaconEmitter app running at opposite corners of a room. Have one configured to minor value 0 and the other to minor value 1. While no beacon is active, the YardSale app will show five table view sections. Once you start a software iBeacon, you’ll only see this one. If you have multiple iBeacons active, the YardSale app will always show the table view section corresponding to the closer beacon.

iBeacon ranging supposedly consumes more power than monitoring because iOS needs to determine the signal strengths. Also, it doesn’t work in the background, unlike beacon monitoring. If you send the YardSale app into the background by pressing the home button, the ranging updates are paused. But ranging is more useful when the app is active anyway. You wouldn’t want to bother your user with push notifications every time they got to a different shelf in your store.

7.4.5. Adding an in-store barcode scanner

The final feature we’ll add to the YardSale app brings us back to barcode scanning. Section 6.1.4 explained how to add a modal barcode scanner view controller to your app. Here we’ll repeat the process to let the user get information about a specific product.

You’ve done this before, and you can look at the YardSale app’s code to see it fully implemented. Here’s the list of steps involved in adding the barcode scanner to the YardSale app:

1. Open the MusicCollection app project.

2. Copy all classes from the Copied Code group into your YardSale app project.

3. Copy the navigation controller and camera preview controllers to your storyboard.

4. Add a scan button as the right bar button item of the in-store view controller.

5. Connect a modal segue from this button to the copied navigation controller.

6. Give this new segue the ShowScanner identifier.

7. Add a dummy unwind method to InStoreViewController.m.

8. Connect the scanner view controller’s Cancel button to the unwind method.

9. Set the identifier for the new unwind segue to unwind.

10. Set the scannerDelegate property of the scanner in prepareForSegue:sender:.

11. Implement the delegate method for when a barcode has been scanned.

The two methods mentioned in steps 7 and 10 look like this:

- (IBAction)unwindFromScannerViewController:(UIStoryboardSegue *)segue {

// intentionally left black

}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

if ([segue.identifier isEqualToString:@"ShowScanner"]) {

UINavigationController *nav = [segue destinationViewController];

DTCameraPreviewController *vc = nav.viewControllers[0];

vc.delegate = self;

}

}

The dummy unwind method only serves the purpose of allowing you to create an unwind segue in Interface Builder and to give it an identifier for calling it programmatically. The complete YardSale app storyboard is shown in figure 7.10.

Figure 7.10. Complete YardSale app storyboard

Finally, the implementation for reacting to a scanned barcode dismisses the modal barcode scanner and shows an alert giving details about the context:

While this example just shows an alert, a real-life app could do something more interesting with the multiple layers of context information. You know which store the user is in, which semantic location inside the store the user is next to, and which specific item the user is interested in.

Apple trusts their customers enough to let them purchase small value accessories this way from within the Apple Store app while at a physical Apple store. As more developers become aware of these context-sensing capabilities, there are many more “magical” uses waiting to be discovered.

7.5. Summary

Core Location lets you monitor two kinds of regions: geographic regions defined by a center and radius, and semantic regions defined by iBeacons. You can’t monitor more than 20 regions at one time. The technique presented in this chapter allows you to monitor a virtually unlimited number of yard sale locations by dynamically updating the monitored georegions if the user moves significantly.

In additional to region monitoring for iBeacons, you can also request ranging for them. This uses more energy than beacon region monitoring, but it also gives you an indication about how far individual beacons are away from the user. This information can be useful in determining different contexts that you can present information and actions for as the user moves around the store.

Additional context information can come from allowing the user to scan barcodes in your store. Combining these multiple levels of context can give you insight into what the user might currently be most interested in. If your users get a sense that your app is “smarter” than others, they’ll be more delighted to use it. Ask yourself, what information or actions would be most useful to me in this context?

These are the key takeaways for this chapter:

· Use geographic region monitoring to make your app aware of nearby physical stores.

· Local push notifications are a good way to alert the user, even if your app is inactive.

· While the user is inside a physical store, employ iBeacon ranging to determine a semantic location.

· Let the user interact with physical products by providing a barcode scanner.

· Optimize the displayed information and possible actions for a combination of multiple levels of context.

· Deal gracefully with disabled authorization, non-available monitoring or ranging, and errors.

· Apple introduced several authorization changes for background location updates and sending local notifications. Unless you choose to only support iOS 8, make sure you test your solution under both iOS 7 and iOS 8.

You’ve reached the end of this book, but hopefully not the end of your exploration of barcodes. You learned about barcodes, as well as several iOS technologies that are of great utility when working with barcodes. Possessing this knowledge, you can now rightfully claim the title of “iOS Barcode Guru.”

I love barcodes so much there’s even one on the back cover of this book! Keep your eyes peeled and you’ll begin to notice barcodes everywhere, metaphorically screaming at you to create apps so your users can put them to good use.