iOS SBeacon Framework Initialization - scopetech/Mobile-Public GitHub Wiki

Initialization

iOS SBeacon Framework v1.0.8

This guide covers framework initialization, permission handling, and app lifecycle integration.


Prerequisites

Before initializing the framework, ensure you have:

  • Completed Installation steps
  • Added required Info.plist permissions
  • Configured background modes

SwiftUI App Setup

Basic Initialization

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")
    }
}

UIKit AppDelegate Setup

Traditional AppDelegate Pattern

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
    }
}

Permission Handling

Bluetooth Permissions

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:

  1. User launches app
  2. App calls SBeaconManager.shared.startScan()
  3. iOS shows system dialog: "[App Name] Would Like to Use Bluetooth"
  4. User taps "OK" or "Don't Allow"
  5. 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
    }
}

Location Permissions (Optional)

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
    }
}

Framework Configuration

Basic Configuration

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()
}

Feature Flags

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 = true

Complete Integration Example

SwiftUI App with Permission Handling

import 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
    }
}

UIKit ViewController with Permission Handling

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
    }
}

Background Operation

Enabling Background BLE

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

Best Practices

1. Callback Cleanup

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
}

2. Battery Optimization

  • Stop scanning when not needed (e.g., in viewWillDisappear)
  • Use periodic scanning instead of continuous scanning
  • Limit background BLE operations to essential features

3. Permission Timing

  • Request permissions at appropriate moments (not on launch)
  • Explain why permissions are needed before requesting
  • Handle permission denial gracefully with helpful messaging

4. State Management

  • Check Bluetooth state before starting operations
  • Handle Bluetooth off/unavailable states
  • Observe CBCentralManagerDelegate for state changes

Troubleshooting

Bluetooth Permission Not Appearing

Problem: Permission dialog doesn't show when calling startScan()

Solutions:

  • Ensure NSBluetoothAlwaysUsageDescription is in Info.plist
  • Check that Bluetooth is enabled in device settings
  • Verify app hasn't been denied previously (check Settings > Privacy > Bluetooth)

Crashes on Initialization

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

Background Scanning Not Working

Problem: Scanning stops when app is backgrounded

Solutions:

  • Verify bluetooth-central is in UIBackgroundModes
  • Ensure you're connected to beacons (not just scanning)
  • Note: New beacon discovery doesn't work in background (iOS limitation)

Callbacks Not Firing

Problem: Callbacks never execute

Solutions:

  • Check callbacks are set before triggering action
  • Verify strong reference to manager exists
  • Ensure beacon is connected (check isInRange property)

Related Pages

⚠️ **GitHub.com Fallback** ⚠️