iOS SBeacon Framework Initialization - scopetech/Mobile-Public GitHub Wiki
iOS SBeacon Framework v1.0.8
This guide covers framework initialization, permission handling, and app lifecycle integration.
Before initializing the framework, ensure you have:
- Completed Installation steps
- Added required Info.plist permissions
- Configured background modes
For SwiftUI apps (iOS 14+), initialize managers in your App struct:
import SwiftUI
import BLEFramework
@main
struct MyApp: App {
init() {
// Framework managers are singletons and auto-initialize
// Access them to ensure they're ready
_ = SBeaconManager.shared
_ = SCPBeaconManager.shared
// Request permissions on first launch
requestPermissionsIfNeeded()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
private func requestPermissionsIfNeeded() {
// Check if permissions were previously requested
guard !UserDefaults.standard.bool(forKey: "permissions_requested") else {
return
}
// Request Bluetooth permissions (implicit - happens on first BLE operation)
// Bluetooth permission dialog appears automatically when you start scanning
UserDefaults.standard.set(true, forKey: "permissions_requested")
}
}For UIKit-based apps or when using AppDelegate:
import UIKit
import BLEFramework
import CoreBluetooth
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Initialize framework managers
initializeBLEFramework()
// Request permissions
requestPermissions()
return true
}
private func initializeBLEFramework() {
// Singleton managers auto-initialize
// Access them to ensure they're ready
let sbeaconManager = SBeaconManager.shared
let beaconManager = SCPBeaconManager.shared
print("BLE Framework initialized")
}
private func requestPermissions() {
// Bluetooth permissions are requested automatically when scanning starts
// No explicit permission request needed for Bluetooth on iOS
// If using location features, request location permission:
// let locationManager = CLLocationManager()
// locationManager.requestWhenInUseAuthorization()
}
// MARK: - Background Restoration
// This method is called when the app is launched from background due to BLE events
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
// Handle state restoration if needed
return true
}
}Important: iOS handles Bluetooth permissions differently than Android. There is no explicit permission request for Bluetooth. The system automatically shows a permission dialog the first time your app attempts to use Bluetooth (e.g., when you call startScan()).
Permission Flow:
- User launches app
- App calls
SBeaconManager.shared.startScan() - iOS shows system dialog: "[App Name] Would Like to Use Bluetooth"
- User taps "OK" or "Don't Allow"
- Permission granted/denied
Checking Bluetooth Permission Status:
import CoreBluetooth
func checkBluetoothPermission() -> Bool {
let centralManager = SCPBluetoothScannerManager.shared.manager
switch centralManager.authorization {
case .allowedAlways:
print("Bluetooth permission granted")
return true
case .denied, .restricted:
print("Bluetooth permission denied")
return false
case .notDetermined:
print("Bluetooth permission not determined yet")
return false
@unknown default:
return false
}
}Only request if your app uses location features:
import CoreLocation
class PermissionManager: NSObject, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
override init() {
super.init()
locationManager.delegate = self
}
func requestLocationPermission() {
let status = locationManager.authorizationStatus
switch status {
case .notDetermined:
// Request permission
locationManager.requestWhenInUseAuthorization()
case .authorizedWhenInUse:
// Optionally upgrade to Always authorization
locationManager.requestAlwaysAuthorization()
case .authorizedAlways:
print("Location permission granted (Always)")
case .denied, .restricted:
print("Location permission denied")
showSettingsAlert()
@unknown default:
break
}
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
let status = manager.authorizationStatus
print("Location authorization changed: \(status.rawValue)")
}
private func showSettingsAlert() {
// Show alert directing user to Settings
// This would typically be done in your ViewController
}
}import BLEFramework
func configureBLEFramework() {
// Access singleton managers
let sbeaconManager = SBeaconManager.shared
let beaconManager = SCPBeaconManager.shared
// Set up callbacks (see Event Handling guide for details)
sbeaconManager.tripStatusChanged = { [weak self] status in
print("Trip status: \(status)")
}
sbeaconManager.macAddressReceived = { [weak self] macAddress in
print("MAC address received: \(macAddress)")
}
// Start scanning for beacons
sbeaconManager.startScan()
}Enable or disable framework features using BluetoothConfigManager:
// Enable/disable beacon functionality
BluetoothConfigManager.enableBeacon = true
// Enable/disable driver conflict detection
BluetoothConfigManager.isDriverConflictEnabled = true
// Enable/disable beacon connection notifications
BluetoothConfigManager.isBeaconConnectionNotificationEnabled = true
// Enable/disable accident threshold configuration
BluetoothConfigManager.isAccidentThresholdEnabled = trueimport SwiftUI
import BLEFramework
@main
struct BeaconApp: App {
@StateObject private var permissionManager = PermissionManager()
@StateObject private var beaconCoordinator = BeaconCoordinator()
init() {
// Configure feature flags
BluetoothConfigManager.enableBeacon = true
BluetoothConfigManager.isDriverConflictEnabled = true
}
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(permissionManager)
.environmentObject(beaconCoordinator)
.onAppear {
// Request permissions when app appears
permissionManager.requestPermissionsIfNeeded()
}
}
}
}
// MARK: - Permission Manager
class PermissionManager: ObservableObject {
@Published var bluetoothAuthorized = false
@Published var locationAuthorized = false
func requestPermissionsIfNeeded() {
// Bluetooth permission is requested automatically when scanning starts
// Just track the status
checkBluetoothPermission()
// Request location if needed
// requestLocationPermission()
}
private func checkBluetoothPermission() {
let centralManager = SCPBluetoothScannerManager.shared.manager
bluetoothAuthorized = centralManager.authorization == .allowedAlways
}
}
// MARK: - Beacon Coordinator
class BeaconCoordinator: ObservableObject {
@Published var isScanning = false
init() {
setupCallbacks()
}
func startScanning() {
SBeaconManager.shared.startScan()
isScanning = true
}
func stopScanning() {
SBeaconManager.shared.stopScan()
isScanning = false
}
private func setupCallbacks() {
SBeaconManager.shared.tripStatusChanged = { [weak self] status in
print("Trip status changed: \(status)")
}
SBeaconManager.shared.macAddressReceived = { [weak self] macAddress in
print("Beacon MAC address: \(macAddress)")
}
}
deinit {
// Clean up callbacks to prevent memory leaks
SBeaconManager.shared.tripStatusChanged = nil
SBeaconManager.shared.macAddressReceived = nil
}
}import UIKit
import BLEFramework
import CoreBluetooth
class BeaconViewController: UIViewController {
private var sbeaconManager: SBeaconManager!
private var bluetoothAuthorized = false
override func viewDidLoad() {
super.viewDidLoad()
sbeaconManager = SBeaconManager.shared
setupCallbacks()
checkBluetoothPermission()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Start scanning when view appears
if bluetoothAuthorized {
sbeaconManager.startScan()
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Stop scanning to save battery
sbeaconManager.stopScan()
}
private func checkBluetoothPermission() {
let centralManager = SCPBluetoothScannerManager.shared.manager
switch centralManager.authorization {
case .allowedAlways:
bluetoothAuthorized = true
sbeaconManager.startScan()
case .denied, .restricted:
showPermissionDeniedAlert()
case .notDetermined:
// Permission dialog will appear when we start scanning
sbeaconManager.startScan()
@unknown default:
break
}
}
private func setupCallbacks() {
sbeaconManager.tripStatusChanged = { [weak self] status in
self?.handleTripStatusChanged(status)
}
sbeaconManager.macAddressReceived = { [weak self] macAddress in
print("MAC address received: \(macAddress)")
}
}
private func handleTripStatusChanged(_ status: TripStatus) {
print("Trip status: \(status)")
// Update UI based on trip status
}
private func showPermissionDeniedAlert() {
let alert = UIAlertController(
title: "Bluetooth Permission Required",
message: "Please enable Bluetooth in Settings to use this feature.",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "Settings", style: .default) { _ in
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
}
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
present(alert, animated: true)
}
deinit {
// Clean up callbacks
sbeaconManager.tripStatusChanged = nil
sbeaconManager.macAddressReceived = nil
}
}To continue BLE communication when the app is backgrounded:
1. Configure Info.plist (already done in Installation)
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
</array>2. Handle State Restoration
When iOS terminates and relaunches your app in the background due to BLE events, you can restore state:
// In AppDelegate
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Check if launched due to BLE state restoration
if let peripheralIdentifiers = launchOptions?[.bluetoothCentrals] as? [String] {
print("App launched for BLE state restoration: \(peripheralIdentifiers)")
// Framework automatically handles restoration
// Reconnect to beacons
_ = SBeaconManager.shared
}
return true
}Background Behavior:
- iOS keeps BLE connections alive in background
- Framework automatically reconnects to paired beacons
- Trip status updates continue to fire callbacks
- Real-time driving behavior events are detected
Limitations:
- Scanning for new beacons is paused in background
- iOS may terminate app if memory is needed
- Battery drain increases with background BLE
Always use [weak self] in callbacks to prevent retain cycles:
SBeaconManager.shared.tripStatusChanged = { [weak self] status in
guard let self = self else { return }
self.handleTripStatus(status)
}Set callbacks to nil when done:
deinit {
SBeaconManager.shared.tripStatusChanged = nil
SBeaconManager.shared.macAddressReceived = nil
}- Stop scanning when not needed (e.g., in
viewWillDisappear) - Use periodic scanning instead of continuous scanning
- Limit background BLE operations to essential features
- Request permissions at appropriate moments (not on launch)
- Explain why permissions are needed before requesting
- Handle permission denial gracefully with helpful messaging
- Check Bluetooth state before starting operations
- Handle Bluetooth off/unavailable states
- Observe
CBCentralManagerDelegatefor state changes
Problem: Permission dialog doesn't show when calling startScan()
Solutions:
- Ensure
NSBluetoothAlwaysUsageDescriptionis in Info.plist - Check that Bluetooth is enabled in device settings
- Verify app hasn't been denied previously (check Settings > Privacy > Bluetooth)
Problem: App crashes when accessing managers
Solutions:
- Ensure framework is properly embedded (see Installation)
- Check minimum deployment target is iOS 15.6+
- Verify all required Info.plist keys are present
Problem: Scanning stops when app is backgrounded
Solutions:
- Verify
bluetooth-centralis in UIBackgroundModes - Ensure you're connected to beacons (not just scanning)
- Note: New beacon discovery doesn't work in background (iOS limitation)
Problem: Callbacks never execute
Solutions:
- Check callbacks are set before triggering action
- Verify strong reference to manager exists
- Ensure beacon is connected (check
isInRangeproperty)
- Beacon Scanning - Start discovering beacons
- Event Handling - Complete callback reference
- Installation - Framework setup prerequisites