How to develop an iOS module for iBeacon in React Native?
Contents
In the previous article, we talked about the use of beacons and BLE technology in React Native applications using the react-native-ble library. Now let's move on to a more advanced approach for working with beacons - let's develop a native module for iOS.
Why is it better? You can achieve greater accuracy, deep integration with iOS, and better power management.
In this article, we will compare both approaches and discuss why a native solution is better for certain types of applications. We will also dwell in detail on the technical implementation, setting up beacons and tell you why they are needed for business.
What is the difference between BLE and iBeacon?
Bluetooth Low Energy (BLE) and iBeacon operate on the same technology basis, but are designed for different purposes and have different applications.
BLE (Bluetooth Low Energy)
BLE is designed to transmit small amounts of data over short distances with minimal power consumption. This is ideal for battery-powered devices such as fitness bands, environmental sensors, smartwatches and other Internet of Things (IoT) devices.
The main advantage of BLE is its ability to operate for a long time on a single battery charge due to its low power consumption.
Thanks to BLE, you can create a bunch of different applications that require data transfer between devices over short distances.
iBeacon
iBeacon is a protocol developed by Apple that uses BLE technology to provide geolocation services in specific scenarios. It allows mobile apps to determine their proximity to a beacon, a small BLE device that broadcasts a unique identifier.
Unlike BLE, which is responsible for simple data exchange between devices, iBeacon is used to create more complex interaction scenarios. For example, the application must respond when a user approaches or leaves a specific location.
Benefits of iBeacon
The iBeacon protocol is particularly suitable for applications that require precise and context-sensitive user interactions indoors.
With simplified geofencing setup, better background support, and improved location accuracy. This makes iBeacon ideal for retail marketing, indoor navigation and indoor automation applications.
Simplified geofencing setup
iBeacon makes it easy to create geofences around physical beacons. Developers don't have to worry about the complex setup and maintenance of an indoor GPS network. Because it does not guarantee such accuracy and efficiency. At the same time, iBeacon makes it easy to detect when a user enters or leaves a given geofence.
Better background support
iOS allows the app to run in the background. It will receive a notification when a geofence boundary is crossed even when not in use. Thanks to this, you can, for example, automatically offer coupons or special offers when a person approaches a store. Or track important events for logistics and monitoring applications without active user participation.
Improved indoor location accuracy
In confined spaces such as shopping malls, museums, conference rooms and other indoor locations, GPS is unreliable due to weak signal and lack of accuracy. iBeacon is much better at identifying user locations. This opens the door to the development of more precise indoor navigation systems, allowing, for example, to direct visitors to specific exhibits in a museum or suggest products that are in close proximity to the user in a store.
Beacon
I used the Holyiot nRF52810 beacon. It stands out for its ultra-low power consumption, compact size and waterproof design.
Using the Holyiot-beacon application, you can easily configure the beacon and adapt the parameters to specific needs.
General characteristics
• Signal strength and range:
The beacon is capable of transmitting a signal over a distance of up to 50 meters in open space with adjustable transmission power from -40dB to +4dB. By default, the power is set to +4dB, which optimizes range and power consumption.
• Battery life:
Taking into account the CR2032 battery capacity of 220 mAh and average power consumption of 49uA at +4dB and a transmission interval of 500ms, the beacon provides up to 180 days of operation without battery replacement. This makes the beacon particularly suitable for applications that require long-term deployment without maintenance.
• Waterproof:
The IP67 protection class makes the beacon resistant to water and dust, which expands the possibilities of its use in various conditions.
• Physical parameters:
The beacon has compact dimensions with a diameter of 30 mm and a thickness of 8.4 mm with a weight of only 6.5 g, making it easily integrated into any environment.
Setting up a beacon
1
Download the application
• Open the application store on your smartphone (App Store for iOS or Google Play for Android).
• Search for "Holyiot-beacon" and look for Holyiot's beacon configuration app.
• Install the application on your device
• Search for "Holyiot-beacon" and look for Holyiot's beacon configuration app.
• Install the application on your device
2
Search for device
• Launch the "Holyiot-beacon" application on your smartphone.
• In the application, select the device type to search: beacon, ibeacon or eddystone can be selected depending on your needs.
• The app will start searching for available devices nearby.
• In the application, select the device type to search: beacon, ibeacon or eddystone can be selected depending on your needs.
• The app will start searching for available devices nearby.
3
Connecting to a beacon
• When the beacon is detected, select it from the list of available devices.
• To connect to the beacon and access additional information, click on the "Connect" button.
• When prompted, enter your password to connect. Default password: x`aa14061112`.
• To connect to the beacon and access additional information, click on the "Connect" button.
• When prompted, enter your password to connect. Default password: x`aa14061112`.
4
Beacon configuration
Parameter configuration allows you to fine-tune the beacon's operation to the specific requirements of the application or service, providing the desired balance between visibility, power consumption and positioning accuracy.
After a successful connection, you will be able to view detailed information about the tracker and the parameters available for configuration. In the settings section, select the settings you want to change.
Beacon configuration involves setting various parameters, each of which plays an important role in how the beacon functions and how it interacts with applications and devices. Here are the main parameters that are usually configured when configuring a beacon:
UUID (Universal Unique Identifier)
A UUID is a 128-bit identifier that is used to identify a specific group or category of beacons in a large space. It allows the mobile app to differentiate your organization's beacons.
A UUID is a 128-bit identifier that is used to identify a specific group or category of beacons in a large space. It allows the mobile app to differentiate your organization's beacons.
Major and Minor
Major and Minor are numeric values used to identify a subgroup within a group of beacons with the same UUID. Major usually defines a larger subgroup, and Minor usually defines a smaller one. These options allow you to create additional hierarchy or structuring within your beacon system, making it easier to manage and target interactions within your application.
Major and Minor are numeric values used to identify a subgroup within a group of beacons with the same UUID. Major usually defines a larger subgroup, and Minor usually defines a smaller one. These options allow you to create additional hierarchy or structuring within your beacon system, making it easier to manage and target interactions within your application.
TX Power (Мощность передачи)
TX Power reflects the signal strength with which the beacon transmits its data. This parameter directly affects the beacon's range and energy consumption. Setting the optimal transmit power allows you to balance the visibility of the beacon and the duration of its battery life.
TX Power reflects the signal strength with which the beacon transmits its data. This parameter directly affects the beacon's range and energy consumption. Setting the optimal transmit power allows you to balance the visibility of the beacon and the duration of its battery life.
ADV_interval (Advertising interval)
ADV_interval is the time between successive transmissions of Bluetooth advertising packets from the beacon. The interval affects how often the beacon “announces” itself, which affects power consumption and how quickly applications respond to the beacon appearing or disappearing from range. Decreasing the interval increases the chances of quickly detecting the beacon, but also increases power consumption.
ADV_interval is the time between successive transmissions of Bluetooth advertising packets from the beacon. The interval affects how often the beacon “announces” itself, which affects power consumption and how quickly applications respond to the beacon appearing or disappearing from range. Decreasing the interval increases the chances of quickly detecting the beacon, but also increases power consumption.
Make the necessary changes and save the settings. Some changes may require reconnecting to the beacon or rebooting the beacon.
Implementing a native module in React Native for iOS
When we were developing an application in React Native, we were looking for a solution for working with iBeacons, but we did not find one that fully met our requirements. Therefore, we decided to develop our own native module for iOS in order to make maximum use of the capabilities of this platform for interacting with beacons.
Creating such a module gave us full control over the process of scanning beacons and processing events associated with their detection, ensuring high reliability and accuracy of the application.
Creating such a module gave us full control over the process of scanning beacons and processing events associated with their detection, ensuring high reliability and accuracy of the application.
Creating a native module
First, we create a native module for iOS, which we integrate with React Native. To do this, we write code in Swift or Objective-C, which will be called from JavaScript. This process involves declaring methods that can be called from React Native and setting up an event model for exchanging data between the native and JavaScript parts of the application.
For detailed instructions on creating and integrating native modules into a React Native project, including code examples and best practices, see the official React Native documentation.
Setting up required permissions
After creating a native module, the next step is to configure the necessary permissions and capabilities in your Xcode project to ensure it works correctly with beacons and notifications.
Open your project settings in Xcode and go to the "Signing & Capabilities" section.
Here you need to add two key capabilities: "Background Modes" and "Push Notifications".
• Enabling "Location updates" and "Uses Bluetooth LE accessories" in the "Background Modes" section allows the application to receive location data and work with Bluetooth accessories even when it is in the background.
• Enabling "Push Notifications" is necessary so that the application can send local notifications to the user when entering or leaving the beacon's coverage area.
To work correctly with beacons and Bluetooth in iOS, it is important to add the necessary permission lines to the project's Info.plist file. These lines inform the user how the application intends to use the device's data or functionality.
Here are the key permissions that are typically required:
1
Privacy - Location Always and When In Use Usage Description
(NSLocationAlwaysAndWhenInUseUsageDescription): Requires a description of why the application needs access to the user's location data at any time, including in the background.
For example, "This app uses your location to determine your proximity to beacons of interest, even when the app is in the background."
2
Privacy - Location When In Use Usage Description
(`NSBluetoothAlwaysUsageDescription`): Since the module uses Bluetooth to detect beacons, it is necessary to explain why the application needs constant access to Bluetooth.
Example: "Bluetooth access is used to detect beacons around you to provide relevant information and notifications."
3
Privacy - Bluetooth Always Usage Description
(`NSBluetoothAlwaysUsageDescription`): Since the module uses Bluetooth to detect beacons, it is necessary to explain why the application needs constant access to Bluetooth.
Example: "Bluetooth access is used to detect beacons around you to provide relevant information and notifications."
4
Privacy - Bluetooth Peripheral Usage Description
(`NSBluetoothPeripheralUsageDescription`): This explanation is needed if your application is going to act as a Bluetooth peripheral.
For example: "This app uses Bluetooth to communicate with beacons and other devices in your area."
Implementation
BeaconManager.swift
import CoreBluetooth
import CoreLocation
import React
import UserNotifications
// Declare the BeaconManager class, which implements interfaces for working with React Native and delegates for monitoring beacons and Bluetooth'
@objc(BeaconManager)
class BeaconManager: NSObject, RCTBridgeModule, CLLocationManagerDelegate, CBCentralManagerDelegate
{
// Module name for React Native
static func moduleName() -> String {
return "BeaconManager"
}
private var locationManager: CLLocationManager!
private var beaconRegion: CLBeaconRegion!
public var bridge: RCTBridge!
private var centralManager: CBCentralManager!
// Method for sending local notifications
func sendLocalNotification(with message: String) {
let content = UNMutableNotificationContent()
content.title = message // Notification title
content.body = "This is a region event" // Notification text
content.sound = .default // Notification sound
let request = UNNotificationRequest(
identifier: UUID().uuidString, content: content, trigger: nil) // Create a notification request
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil) // Adding a request to the notification center
}
// Start scanning beacons with the given UUID
@objc func startScanning(_ uuid: String, config: NSDictionary) {
// Request permission to send notifications
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) {
granted, error in
if granted {
print("Notifications allowed")
} else {
print("Notifications not allowed")
}
}
DispatchQueue.main.async {
self.locationManager = CLLocationManager() // Initialize CLLocationManager
self.locationManager.delegate = self // Setting the delegate
self.locationManager.requestAlwaysAuthorization() // Request for permanent access to geolocation
// Check and set settings for background scanning
self.locationManager.allowsBackgroundLocationUpdates = true
self.locationManager.pausesLocationUpdatesAutomatically = false
let uuid = UUID(uuidString: uuid)! // Convert UUID string to UUID
let beaconConstraint = CLBeaconIdentityConstraint(uuid: uuid) // Create a constraint for the beacon
self.beaconRegion = CLBeaconRegion(
beaconIdentityConstraint: beaconConstraint, identifier: "BeaconManagerRegion") // Initialize the beacon region
self.beaconRegion.notifyOnEntry = true // Notification when entering a region
self.beaconRegion.notifyOnExit = true // Notification when exiting a region
self.locationManager.startMonitoring(for: self.beaconRegion) // Start monitoring the region
self.locationManager.startRangingBeacons(in: self.beaconRegion) // Start determining the distance to beacons in the region
}
}
// Stop scanning beacons
@objc func stopScanning() {
if let beaconRegion = self.beaconRegion {
self.locationManager.stopMonitoring(for: beaconRegion) // Stop monitoring the region
self.locationManager.stopRangingBeacons(in: beaconRegion) // Stop determining the distance to beacons
self.beaconRegion = nil // Reset the beacon region
self.locationManager = nil // Reset CLLocationManager
}
}
// Initialize the Bluetooth manager
@objc func initializeBluetoothManager() {
centralManager = CBCentralManager(
delegate: self, queue: nil, options: [CBCentralManagerOptionShowPowerAlertKey: false])
}
// Handle Bluetooth state changes
func centralManagerDidUpdateState(_ central: CBCentralManager) {
var msg = ""
switch central.state {
case .unknown: msg = "unknown"
case .resetting: msg = "resetting"
case .unsupported: msg = "unsupported"
case .unauthorized: msg = "unauthorized"
case .poweredOff: msg = "poweredOff"
case .poweredOn: msg = "poweredOn"
@unknown default: msg = "unknown"
}
bridge.eventDispatcher().sendAppEvent(withName: "onBluetoothStateChanged", body: ["state": msg]) // Send Bluetooth state change event to React Native
}
// Request for permanent access to geolocation
@objc func requestAlwaysAuthorization(
_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock
) {
let locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
let status = CLLocationManager.authorizationStatus()
let statusString = statusToString(status)
resolve(["status": statusString])
}
// Request for access to geolocation when using the application
@objc func requestWhenInUseAuthorization(
_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock
) {
let locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
let status = CLLocationManager.authorizationStatus()
let statusString = statusToString(status)
resolve(["status": statusString])
}
// Get the current geolocation permission status
@objc func getAuthorizationStatus(
_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock
) {
let status = CLLocationManager.authorizationStatus()
resolve(statusToString(status))
}
// Handling region entry and region exit events
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
if let beaconRegion = region as? CLBeaconRegion {
sendLocalNotification(with: "Entered region: \(region.identifier)") // Send a notification about entering a region
if let bridge = self.bridge {
bridge.eventDispatcher().sendAppEvent(
withName: "onEnterRegion", body: ["region": beaconRegion.identifier]) // Dispatch a region entry event in React Native
}
}
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
if let beaconRegion = region as? CLBeaconRegion {
sendLocalNotification(with: "Exit region: \(region.identifier)") // Send a notification about leaving the region
if let bridge = self.bridge {
bridge.eventDispatcher().sendAppEvent(
withName: "onExitRegion", body: ["region": beaconRegion.identifier]) // Send a region exit event to React Native
}
}
}
// Handle detection of beacons in the region
func locationManager(
_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion
) {
let beaconArray = beacons.map { beacon -> [String: Any] in
return [
"uuid": beacon.uuid.uuidString, // UUID of the beacon
"major": beacon.major.intValue, // Major beacon value
"minor": beacon.minor.intValue, // Minor beacon value
"distance": beacon.accuracy, // Accuracy of the distance to the beacon
"rssi": beacon.rssi, // Beacon signal strength
]
}
if let bridge = bridge {
bridge.eventDispatcher().sendAppEvent(withName: "onBeaconsDetected", body: beaconArray) // Send data about detected beacons to React Native
}
}
// Handle geolocation permission changes
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
if #available(iOS 14.0, *) {
if manager.authorizationStatus == .authorizedAlways
|| manager.authorizationStatus == .authorizedWhenInUse
{
locationManager.startMonitoring(for: beaconRegion) // Start monitoring the region
locationManager.startRangingBeacons(in: beaconRegion) // Start determining the distance to beacons
}
} else {
if CLLocationManager.authorizationStatus() == .authorizedAlways
|| CLLocationManager.authorizationStatus() == .authorizedWhenInUse
{
locationManager.startMonitoring(for: beaconRegion)
locationManager.startRangingBeacons(in: beaconRegion)
}
}
}
//Helper method to convert geolocation permission status to a string
private func statusToString(_ status: CLAuthorizationStatus) -> String {
switch status {
case .notDetermined: return "notDetermined"
case .restricted: return "restricted"
case .denied: return "denied"
case .authorizedAlways: return "authorizedAlways"
case .authorizedWhenInUse: return "authorizedWhenInUse"
@unknown default: return "unknown"
}
}
}
BeaconManagerBridge.m
#import "React/RCTBridgeModule.h"
@interface RCT_EXTERN_MODULE(BeaconManager, NSObject)
RCT_EXTERN_METHOD(startScanning:(NSString *)uuid config:(NSDictionary *)config)
RCT_EXTERN_METHOD(stopScanning)
RCT_EXTERN_METHOD(requestAlwaysAuthorization:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(requestWhenInUseAuthorization:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(getAuthorizationStatus:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(initializeBluetoothManager)
+ (BOOL)requiresMainQueueSetup {
return YES;
}
@end
App.js
import React, {useEffect, useState} from 'react';
import {View, NativeModules, Text} from 'react-native';
import {DeviceEventEmitter} from 'react-native';
const {BeaconManager} = NativeModules;
BeaconManager.requestAlwaysAuthorization();
BeaconManager.startScanning('FDA50693-A4E2-4FB1-AFCF-C6EB07647825');
const App = () => {
const [inRegion, setInRegion] = useState(false);
useEffect(() => {
DeviceEventEmitter.addListener('onBeaconsDetected', beacons => {
console.log('onBeaconsDetected', beacons);
});
}, []);
useEffect(() => {
DeviceEventEmitter.addListener('onEnterRegion', beacons => {
setInRegion(true);
});
}, []);
useEffect(() => {
DeviceEventEmitter.addListener('onExitRegion', beacons => {
setInRegion(false);
});
}, []);
return (
< View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 32,
backgroundColor: inRegion ? '#62BB46' : '#472F92',
}}>
<Text style={{color: '#fff', fontWeight: 600, fontSize: 16}}>
{inRegion
? 'You are within the range of the beacon'
: 'You are out of range of the beacon'}
</Text>
</View>
);
};
export default App;
Working with beacons in the background
The `didEnterRegion` and `didExitRegion` methods are part of the `CLLocationManagerDelegate` protocol and are called when a device enters or leaves the beacon's range, respectively. These techniques allow apps to respond to user location changes even in the background.
Limitations and Features
⭕ iOS restrictions on background work
Although iOS allows apps to monitor beacons in the background, there are certain limitations related to battery conservation and device performance.
Apple may limit the frequency of location updates or temporarily pause background app activity to optimize system performance. iOS also provides a limited time to perform the necessary actions in response to this event. This time may vary, but is usually a few seconds. During this time, the application must have time to complete all necessary operations.
⭕ Restrictions on sending notifications
While the methods allow you to respond to regions entering and leaving the background, sending notifications to the user is also subject to strict iOS rules. The application must obtain appropriate permission from the user to send notifications.
⭕ Response delays
Depending on system settings and environmental conditions, there may be delays between actually entering or leaving a region and when the appropriate methods are called. This is due to both the operating features of Bluetooth and iOS power consumption optimization algorithms.
⭕ Beacon accuracy and configuration requirements
To perform optimally in the background, it is important to ensure that the beacons are configured correctly and that the application is configured to work with the required location accuracy.
Working with beacons in the background significantly expands the functionality of applications, allowing you to create advanced solutions for location services, navigation inside buildings, automated user interaction and much more. However, when developing such applications, it is important to consider the features and limitations mentioned above to ensure the best performance and user experience.
If you are interested in this topic and want to figure out how you can use beacons in life, read our article "Interaction with customers offline via mobile app"
You may also like
FoodTech mobile apps: What is important to know about?
Natalie Sokolova, communications expert
Foodtech
Restaurant App Development
Delivery Management
30.01.202413 minutes
AI in foodtech: plans, ideas, features
Natalie Sokolova, communications expert
Foodtech
AI
Delivery Management
25.07.202310 minutes
Using BLE technology when working with beacon in React Native
Maria Sidorevich, Lead Mobile Developer
Foodtech
Delivery Management
React Native
BLE technology
22.03.202415 minutes
Grocery Ecommerce: How to Run a Successful Online Store. Instacart and Freshdirect example
Artur Valokhin, lead frontend developer
Restaurant App Development
Grocery Solutions
Loyalty program
Foodtech
12.04.202418 minutes
Interaction with customers offline via mobile app
Maria Sidorevich, Lead Mobile Developer
Foodtech
Restaurant App Development
BLE technology
18.03.202410 minutes
On-Demand Food Delivery Platforms - Market, Trends & Opportunities
Max Bantsevich, CEO
Foodtech
Delivery Management
E-commerce
Startup
06.12.202320 minutes