0

I am working on an app that uses iBeacons with CLLocation and the light blue bean ios sdks. If I run the app with the battery out of the beacon the app works well. If I then insert the battery to the beacon the app detects the beacon and works well. The problem is when I remove the battery from the beacon ( or when no beacon is detected or beacon is out of range) the app crashes and gives me the following error,

fatal error: Array index out of range.

I understand this error is coming from the CLLocationManagerDelegate method didRangeBeacons, but Im not sure how to prevent my app from crashing? my code is below,

func locationManager(manager: CLLocationManager!, didRangeBeacons beacons: [AnyObject]!, inRegion region: CLBeaconRegion!) {

    self.listBeans = beacons;
    NSNotificationCenter.defaultCenter().postNotificationName("updateBeaconTableView", object: self.listBeans)

    if beacons.count == 0{
        println("no beacons nearby")
        manager.stopUpdatingLocation()
        manager.stopMonitoringForRegion(lightBlueRegion)

    }else{

        let knownBeacons = beacons.filter{$0.proximity != CLProximity.Unknown} // Proximity = 0
        let closestBeacon = knownBeacons[0] as! CLBeacon
        if(closestBeacon.proximity == lastProximity ||
            closestBeacon.proximity == CLProximity.Unknown) {
                return;
        }
        lastProximity = closestBeacon.proximity;

        if (knownBeacons.count > 0){

            switch closestBeacon.proximity {
            case CLProximity.Far:
                println("You are far away from the beacon")

            case CLProximity.Near:
                println("You are near the beacon")

            case CLProximity.Immediate:
                println("You are in the immediate proximity of the beacon")

            case CLProximity.Unknown:
                println("The proximity of the beacon is Unknown")

            }
        } else {
            println("No beacons are nearby")
        }

    }
}

func locationManager(manager: CLLocationManager!, didEnterRegion region: CLRegion!) {

    /* detected every one second */
    println("Region discovered")
    var enteredRegion = true
}

func locationManager(manager: CLLocationManager!, didExitRegion region: CLRegion!) {

    var enteredRegion = false
}


func locationManager(manager: CLLocationManager!, didDetermineState state: CLRegionState, forRegion region: CLRegion!) {

    switch state {
    case CLRegionState.Inside:
        /* In the .Inside case first notification is
        displayed when the user is inside the region
        and opens an app.

        Second notification is shown when the app is in background
        and the user enters the region*/
        if enteredRegion == true{
            message = "didDetermineState = INSIDE a region"
        }
        sendLocalNotificationWithMessage(message)

    case CLRegionState.Outside:
        /* Similar logic follows the .Outside case. It should be noted that the further you are from the beacons the longer it will take for the signal to propagate which means it may take few seconds for the notifications to be displayed when you leave or enter the region.*/

        if enteredRegion == false{
            message = "didDetermineState = OUTSIDE a region"
        }
        sendLocalNotificationWithMessage(message)

    case CLRegionState.Unknown:
        sendLocalNotificationWithMessage("didDetermineState = unknown Region")

    default:
        break;
    }
}


func beanManagerDidUpdateState(beanManager: PTDBeanManager!) {

    switch beanManager.state {
    case .Unsupported:
        var unsupportedAlert = UIAlertController(title: "Error", message: "This device is unsupported.", preferredStyle: UIAlertControllerStyle.Alert)

        unsupportedAlert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))

        self.presentViewController(unsupportedAlert, animated: true, completion: nil)

    case .PoweredOff:

        var PpoweredOffAlert = UIAlertController(title: "Error", message: "Please turn on Bluetooth.", preferredStyle: UIAlertControllerStyle.Alert)

        PpoweredOffAlert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))

        self.presentViewController(PpoweredOffAlert, animated: true, completion: nil)

    case .PoweredOn:
        beanManager.startScanningForBeans_error(nil);
    default:
        break
    }
}

func beanManager(beanManager: PTDBeanManager!, didDiscoverBean bean: PTDBean!, error: NSError!) {

    self.beanName = bean.name as String
    self.beanUUIDNum = bean.identifier.description
}

Thanks

UPDATE:

I ended up with the following code,

let knownBeacons:AnyObject? = beacons.filter{$0.proximity != CLProximity.Unknown} // Proximity = 0

    if let knownBeacons_:AnyObject = knownBeacons {

        if knownBeacons_.count == 0 {
            return
        }

    if let closestBeacon = knownBeacons_[0] as? CLBeacon{
            if(closestBeacon.proximity == lastProximity ||
                closestBeacon.proximity == CLProximity.Unknown) {
                    return;
            }
            lastProximity = closestBeacon.proximity;

            if (knownBeacons_.count > 0){

                switch closestBeacon.proximity {
                case CLProximity.Far:
                    println("You are far away from the beacon")

                case CLProximity.Near:
                    println("You are near the beacon")

                case CLProximity.Immediate:
                    println("You are in the immediate proximity of the beacon")

                case CLProximity.Unknown:
                    println("The proximity of the beacon is Unknown")

                }
            } else {
                println("No beacons are nearby")
            }
        }else{
            println("no beacons nearby")
            manager.stopUpdatingLocation()
            manager.stopMonitoringForRegion(lightBlueRegion)


        }


    }
Jaxs_ios
  • 48
  • 10

2 Answers2

2

If knownBeacons is empty, your code will crash. Before trying to access knownBeacons[0], you need to check whether or not the array is empty. Try wrapping more code in the if (knownBeacons.count > 0) block for more safety, like this.

    let knownBeacons = beacons.filter{$0.proximity != CLProximity.Unknown} // Proximity = 0

    if (knownBeacons.count > 0){

        let closestBeacon = knownBeacons[0] as! CLBeacon
        if(closestBeacon.proximity == lastProximity || closestBeacon.proximity == CLProximity.Unknown) {
            return;
        }
        lastProximity = closestBeacon.proximity;


        switch closestBeacon.proximity {
        case CLProximity.Far:
            println("You are far away from the beacon")

        case CLProximity.Near:
            println("You are near the beacon")

        case CLProximity.Immediate:
            println("You are in the immediate proximity of the beacon")

        case CLProximity.Unknown:
            println("The proximity of the beacon is Unknown")

        }
    }
Will M.
  • 1,864
  • 17
  • 28
1

Red flag here:

let closestBeacon = knownBeacons[0] as! CLBeacon

You're force-unwrapping knownBeacons[0] even though the array could be empty (and the object at index 0 would be nil/you're going out of bounds of the array). Consider using if let:

if let closestBeacon = knownBeacons[0] as! CLBeacon {
    if(closestBeacon.proximity == lastProximity ||
        closestBeacon.proximity == CLProximity.Unknown) {
            return;
    }
} else {
    // handle no beacons case
}
JAL
  • 41,701
  • 23
  • 172
  • 300
  • What your saying makes sense. However, Im still getting the same result when I made the changes and tested it. – Jaxs_ios Aug 07 '15 at 20:15
  • @JAL You can't use an `if let` to access an out of bounds index for an array, it will still crash. – Will M. Aug 07 '15 at 20:32
  • 1
    knownBeacons[0] doesn't return nil, it just crashes, as it is out of bounds. http://stackoverflow.com/questions/25329186/safe-bounds-checked-array-lookup-in-swift-through-optional-bindings – Will M. Aug 07 '15 at 20:44