I am trying to make an app thats shows your speed (velocity) on a gauge (like in a car).
the problem I have is that CLlocationManager delegate method didUpdateLocations gives me a read of the location and the speed only once a second and so if I accelerate the car too fast i get delay with the needle position on the gauge.
for example: I get the speed 13 m/s and after one second i get 26 m/s and so the needle in the UI goes to 13 m/s and waits there for a second and only then goes to 26 m/s. so i get a non continuous movement of needle in the UI.
from reading other question i understood that i can't control the sample rate that I get from CLlocation and that i can't tell him to give me a read every 0.25 sec (with different values).
1. is is true?
2. how can I solve it so it will look good to the user?
//in viewdidload:
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
self.locationManager.distanceFilter = kCLDistanceFilterNone
if versionNum == 8 {
self.locationManager.requestAlwaysAuthorization()
}
self.locationManager.startUpdatingLocation()
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
println(locations)
var loc = locations.last as! CLLocation
var speed = loc.speed
var time = loc.timestamp
var hour = getCurrentHour(time)
println("hour inside manager: \(hour)")
if hour == 18 && self.isItDay {
self.currentTimeHour = hour
self.setDayBool()
self.setRightImg()
}
else if hour == 6 && !self.isItDay {
self.currentTimeHour = hour
self.setDayBool()
self.setRightImg()
}
if self.unit == "MPH" {
speed = speed * 3.280839895013123
}
if speed >= 0 {
self.speedLabel.text = "\(Int(speed))"
self.animateNeedle(CGFloat(speed))
}
println("speed: \(speed)")
}
func animateNeedle(speed : CGFloat) {
var rotation : CGFloat = 0
if self.unit == "KMH" {
rotation = (speed * 4) - 5
}
else if self.unit == "MPH" {
rotation = speed + ((speed / 3) - 5)
}
if let trans = self.needleImg.layer.presentationLayer() {
self.currentValue = self.needleImg.layer.presentationLayer().valueForKeyPath("transform.rotation") as! Float
}
println(currentValue)
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fillMode = kCAFillModeBoth
rotateAnimation.removedOnCompletion = false
rotateAnimation.fromValue = currentValue
rotateAnimation.toValue = self.degToRad(rotation)
rotateAnimation.duration = 0.3
self.needleImg.layer.addAnimation(rotateAnimation, forKey: "transform.rotation")
}
As Lefteris says, you should set your distance filter to 0. You should also select kCLLocationAccuracyBest as the desiredAccuracy property of your location manager.
iOS GPS is rather crude in any case, so I doubt if either of those things will help a whole lot.
I would suggest animating your speedometer needle from it's old position to the new. A duration of .2 - .3 seconds should be enough to create the impression of movement without taking so long that it interferes with keeping the needle position current.
You can use UIView animation methods like animateWithDuration:animations, or Core Animation. Core Animation is a lot harder to use but offers more options.
You need to set the distance filter that notify changes:
/* Notify changes when device has moved x meters.
* Default value is kCLDistanceFilterNone: all movements are reported.
*/
self.locationManager.distanceFilter = 10.0f;
From Apple Documentation:
For more option look at this full example:
self.locationManager = [CLLocationManager new];
self.locationManager.purpose = #"Tracking your movements on the map.";
self.locationManager.delegate = self;
/* Pinpoint our location with the following accuracy:
*
* kCLLocationAccuracyBestForNavigation highest + sensor data
* kCLLocationAccuracyBest highest
* kCLLocationAccuracyNearestTenMeters 10 meters
* kCLLocationAccuracyHundredMeters 100 meters
* kCLLocationAccuracyKilometer 1000 meters
* kCLLocationAccuracyThreeKilometers 3000 meters
*/
self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
/* Notify changes when device has moved x meters.
* Default value is kCLDistanceFilterNone: all movements are reported.
*/
self.locationManager.distanceFilter = 10.0f;
/* Notify heading changes when heading is > 5.
* Default value is kCLHeadingFilterNone: all movements are reported.
*/
self.locationManager.headingFilter = 5;
// update location
if ([CLLocationManager locationServicesEnabled]){
[self.locationManager startUpdatingLocation];
}
You need to set the distance filter to 0:
self.locationManager.distanceFilter = kCLDistanceFilterNone;
Also as Massimo mentioned above, you should also change the desired accuracy:
self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
Related
Sometimes location manager gives GPS-point, which is located about hundreds meters, or several kilometers away from real device location. After it location manager continues provide us with wrong coordinates, which slowly became closer to real device location.
Such points have quite good accuracy about ~30 - ~65 meters, and some of them have speed > 0 m/s.
real path was:
Authorization status is "Always allow"
location manager is adjustment:
CLLocationManager *manager = [CLLocationManager new];
manager.allowsBackgroundLocationUpdates = YES;
manager.pausesLocationUpdatesAutomatically = NO;
manager.showsBackgroundLocationIndicator = YES;
manager.distanceFilter = 50; // meters
manager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
manager.delegate = self;
This problem is actual only for iOS 13. I made about 50 experiments, registering movement trajectories of ~5 km - ~150 km length. Same code works perfectly with iOS 12.
I have an app that tracks the journeys of the user.
I noticed that in background, from iOS 13.3, the system slows down the sending of locations when the speed slows down. In case of high speed, the location are sent every second. Is this possible? Could it be a bug? This is my location manager:
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.activityType = .other
locationManager.allowsBackgroundLocationUpdates = true
I was faced with this question in one of my interviews and was completely stumped. The only solution I could think of was storing the currentAngle in a NSArray to calculate the next angle.
Question:
Move a 35px ball across the screen utilizing the iPhone's compass. Once the ball is in the center of the screen, let the user tap it to 'reset' the position. Once reset, the ball will go back to the Min position. Remember that the compass may start somewhere between 0-359, the task is to find the nearest capture angle and focus on that angle until the ball is aligned. Once the ball is aligned & reset, the iPhone will move to the next angle and so forth until the ball has been reset 18 times. 18 resets * 20 degree angles = 360.
Assigned Variables:
int currentAngle = (Ranging between 0-359) (Constant updates as the user twirls around)
int captureAngle = 20
int centerX = view.center.x (160) - 35 (size of ball)
int ballSize = 35 (ball.width/2)
The paper looked something like this:
Function so far:
-(void)testMotion{
motionQueue = [[NSOperationQueue alloc] init];
motionManager = [[CMMotionManager alloc] init];
motionManager.deviceMotionUpdateInterval = 1.0f / 60.0f;
if (([CMMotionManager availableAttitudeReferenceFrames] & CMAttitudeReferenceFrameXTrueNorthZVertical) != 0) {
[motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXTrueNorthZVertical
toQueue:motionQueue
withHandler:^(CMDeviceMotion *motion, NSError *error)
{
if (!error) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
CMAttitude *attitude = motion.attitude;
CMRotationMatrix rm = attitude.rotationMatrix;
// Get the heading.
double heading = M_PI + atan2(rm.m22, rm.m12);
heading = heading*180/M_PI;
int currentAngle = (int)heading;
NSLog(#"Current Angle: %d",currentAngle);
int captureAngle = 20; // 20 Degress Capture Angle
}];
}
}];
}
}
If I understood you, then it's something like this:
calculate the x and y movement from the angle
(see
https://en.wikipedia.org/wiki/Rotation_of_axes
http://keisan.casio.com/has10/SpecExec.cgi?id=system/2006/1223522781
)
Then move the ball according to those values, and if it moved by an angle of 20 - allow reset it or get out of the loop (for your choice)
while(1) {
x = r \cos(currentAngle)
y = r \sin(currentAngle)
//change the ball position,
ball.position.x += x*speed
ball.position.y += y*speed
//check if angel is +20 or -20
if (((currentAngle + 20) % 360) != captureAngle && (abs(currentAngle - 20) % 360) != captureAngle)) {
allow_reset_ball = true
break;
}
}
I am building a compass into an IOS app that I'm developing. Currently I am building on iPad but am facing a huge issue when switching orientations whilst on the view
IF I open the view while the app is already in landscape/portrait mode, the compass appears perfectly fine (like 1st image) however IF i go in Portrait mode for example and then turn to landscape, the whole UI of the compass goes crazy (see second image)
My code in ViewDidLoad looks something like this:
//start updating compass
locationManager=[[CLLocationManager alloc] init];
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.headingFilter = 1;
locationManager.delegate=self;
[locationManager startUpdatingHeading];
//get coords of current location
CLLocation *location = [locationManager location];
CLLocationCoordinate2D fromLoc = [location coordinate];
//mecca:
CLLocationCoordinate2D toLoc = [location coordinate];
toLoc = CLLocationCoordinate2DMake(21.4167, 39.8167);
//calculate the bearing between current location and Mecca
float fLat = degreesToRadians(fromLoc.latitude);
float fLng = degreesToRadians(fromLoc.longitude);
float tLat = degreesToRadians(toLoc.latitude);
float tLng = degreesToRadians(toLoc.longitude);
float degree = radiandsToDegrees(atan2(sin(tLng-fLng)*cos(tLat), cos(fLat)*sin(tLat)-sin(fLat)*cos(tLat)*cos(tLng-fLng)));
if (degree >= 0) {
bearing = degree;
} else {
bearing = degree+360;
}
NSLog(#"bearing: %f", bearing);
//rotate the needle from true north
float MnewRad = degreesToRadians(bearing);
needleImage.transform = CGAffineTransformMakeRotation(MnewRad);//rotate the number of degrees from north
}
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading {
//compass
float oldRad = -manager.heading.trueHeading * M_PI / 180.0f;
float newRad = -newHeading.trueHeading * M_PI / 180.0f;
CABasicAnimation *theAnimation;
theAnimation=[CABasicAnimation animationWithKeyPath:#"transform.rotation"];
theAnimation.fromValue = [NSNumber numberWithFloat:oldRad];
theAnimation.toValue=[NSNumber numberWithFloat:newRad];
theAnimation.duration = 0.3f;
[compassView.layer addAnimation:theAnimation forKey:#"animateMyRotation"];
compassView.transform = CGAffineTransformMakeRotation(newRad);
}
I'd really appreciate some help with this because its driving me crazy! Thank you
I am making a Qibla functionality for another app, and have compass issues, but I have to say your issue is completely unrelated to the compass functionality.
You said, when you rotate the device, the views get messed up. That seems like an obvious view layout problem. Depending on whether you are using auto layout or not, I would at least suggest that you add all the compass views into a single view container, first. That view should have a fixed size and centered in the view. (aka UIAutoResizingMaskFlexibleTopMargin, BottomMargin, RightMargin, LeftMargin).
Then, I am not sure if the rotation is working well for you, but I use transform.rotation.z, because the rotation happens on the z axis.
Finally, I suggest you would make the animation duration equal to the location update period, i.e duration = 0.1 or something
btw, I switched from using CoreLocation and moved to CoreMotion. It has a simpler compass API, and is much less resource demanding.
If you need to see code, here is my old code that uses CoreLocation:
- (int)fixOrientation {
switch ([[UIApplication sharedApplication] statusBarOrientation]) {
case UIInterfaceOrientationPortrait:
return 0;
case UIInterfaceOrientationLandscapeLeft:
return 90;
case UIInterfaceOrientationLandscapeRight:
return 270;
case UIInterfaceOrientationPortraitUpsideDown:
return 180;
}
}
CLHeading* newHeading = [MCLocationTracker sharedTracker].currentHeading;
[self dismissAlert];
float headingFloat = - newHeading.magneticHeading;
orientationCorrection = [self fixOrientation];
double angle = [[MCLocationTracker sharedTracker] qiblaBearing];
[UIView animateWithDuration:0.05f
delay:0.f
options:(UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState)
animations:^{
self.compassImage.transform = CGAffineTransformMakeRotation(toRadian((headingFloat + orientationCorrection)));
if (fabs(kMCLocTrackerUndefinedAngle - angle) > 0.1f) {
self.arrow.transform = CGAffineTransformMakeRotation(toRadian((headingFloat+angle) + orientationCorrection));
}
}
completion:NULL];
didUpdateHeading is called multiple times per second, so you don't need any animation here. I suspect you aren't seeing anything dues to the 0.3f second animation lag. Set your angle rotation to the new value directly and it will still look smooth.
I need to calculate "facing" (it doesn't matter if it will be based on true north or magnetic one). As it can be seen on the iOS devices the CLHeading objects returned by the CLLocationManager gives us both the true and the magnetic heading by corresponding properties. Also, we can very easily see, that those values are related to the top of the device (the positive Y axis of the devices coordinate system) which is not good for my purposes.
What I actually need is to calculate the facing related to the screen of the device (Z axis) as I don't need the compass, but a king of AG application. The issue is when you rotate the device to landscape you get heading values to the left or to the right from your facing direction, which is what I need in the end.
As I know, I can get the magnetometer "raw" data (given to me in microtesla units with values from 128 to -128 for each device axis) along with the gyroscope "raw" data ( which comes in three types: Euler angels, Rotation matrix or Quaternion). What I need is to know, which calculations I need to apply to those to get the "facing" direction instead of "heading".
I've made it a while ago and because I see no answers, I've decided to put my solution here for those who'll search answer for the same question...
_motionManager = [[CMMotionManager alloc]init];
if (_motionManager.gyroAvailable) {
_motionManager.deviceMotionUpdateInterval = 1.0/20.0;
[_motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue]
withHandler:^(CMDeviceMotion *motion, NSError *error)
{
CMAcceleration gravity = motion.gravity;
CGPoint tiltVector = CGPointMake(-gravity.x, -gravity.y);
_tiltAngle = [self angleYAxisToVector:tiltVector];
CLLocationDirection heaqding = [[SVSession sharedSession] heading].trueHeading;
double newHeading = fmod(heaqding + _tiltAngle, 360.0);
self.azimuth = degreesToRadian(newHeading);
[self updateLocations]; //this function updates my ui for the new heading
}];
} else {
NSLog(#"No gyroscope on device.");
[_motionManager release],_motionManager = nil;
}
And here are some additional snippets that may help to understand this example:
-(double)angleYAxisToVector:(CGPoint)vector{
double dX = vector.x;
double dY = vector.y;
if(dY == 0){
if(dX > 0){
return 0.0;
}else{
if(dX < 0){
return 180.0;
}else{
return -1;
}
}
}
double beta = radiansToDegrees(atan(dX/dY));
double angle;
if(dX > 0){
if (dY < 0){
angle = 180 + beta;
}else{
angle = beta;
}
}else{
if (dY < 0){
angle = 180 + beta;
}else{
angle = 360 + beta;
}
}
// NSLog(#"angle = %f, normalized = %f",beta,angle);
return angle;
}
#define degreesToRadian(x) (M_PI * (x) / 180.0)
#define radiansToDegrees(x) ((x) * 180.0 / M_PI)
#define degreesToRadians(x) degreesToRadian(x)
#define radiansToDegree(x) radiansToDegrees(x)
Happy coding...