Error handling Beacon scanning
Throwing Swift errors is a mechanism how a function/method can report a failure to its caller. Your code throws and catches the error within the
same method, and I can see no advantage of using try/catch in that
situation. Your code is equivalent to

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    guard status == .authorizedWhenInUse else {
        print("User has not authorized us to use location")
        return
    }
    guard CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self) else {
        print("User has not authorized us to use location")
        return
    }
    guard CLLocationManager.isRangingAvailable() else {
        print("User's device does not support ranging")
        return
    }
    startScanning()
}


which is


shorter and easy to understand,
makes the enum LocationError obsolete (which is fileprivate,
and therefore apparently not used anywhere else),
makes the catch-all with fatalError() obsolete.


I would even go a step further: guard is useful in connection with
optional binding (to avoid the "optional binding pyramid of doom").
In your case, the same can be achieved with a simple if/else if/.../else statement:

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    if status != .authorizedWhenInUse {
        print("User has not authorized us to use location")
    } else if !CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self) {
        print("User has not authorized us to use location")
    } else if !CLLocationManager.isRangingAvailable() {
        print("User's device does not support ranging")
    } else {
        startScanning()
    }
}