// // MainView.m // Geopher Lite // // Created by Jeremy on 7/5/08. // Copyright Stone Software 2008. All rights reserved. // #import "MainView.h" #import "Calculations.h" #import "LocatorModel.h" #import "RootViewController.h" @implementation MainView - (void) moveLabel:(UILabel*)label withOffsetX:(NSInteger)offsetX andOffsetY:(NSInteger)offsetY { [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:0.0]; [UIView setAnimationDelay:0.0]; CGAffineTransform transform = CGAffineTransformMakeTranslation(offsetX, offsetY); // move it label.transform = transform; [UIView commitAnimations]; } - (id)initWithCoder:(NSCoder*)coder { if ( (self = [super initWithCoder:coder]) ) { // compass wheel graphic compass = [self newCompassViewWithImageNamed:@"Compass.png" atPostion:self.center]; [self addSubview:compass]; compassArrow = [self newCompassViewWithImageNamed:@"Arrow.png" atPostion:self.center]; [self addSubview:compassArrow]; // Add the label as a subview. [self addSubview:distanceToTarget]; [LocatorModel sharedInstance].delegateView = self; // Check to see if the user has disabled location services all together if ( ! [CLLocationManager locationServicesEnabled] ) { NSLog(@"User disabled location services"); // need an alert or something to the user here! } } return self; } - (NSString*) distanceText:(NSInteger)skinType { switch(skinType) { case 5: return @"Distance"; break; default: return kDistanceEnabledText; break; } } - (void) updateDisplay:(BOOL)newTarget updateCompass:(BOOL)compassUpdate { if (YES == [[LocatorModel sharedInstance] hasValidTarget]) { distanceLabel.text = [self distanceText:viewSkin]; distanceToTarget.text = [Calculations distToDisplayInDecimal:[LocatorModel sharedInstance].distanceToTarget]; } // remove arrow graphic if we don't have a target. if (NO == [[LocatorModel sharedInstance] hasValidTarget]) { distanceLabel.text = kEmptyText; distanceToTarget.text = kEmptyText; } if ([[LocatorModel sharedInstance] hasValidTarget] == YES) { targetLatitudeLabel.text = [[LocatorModel sharedInstance] targetLatitudeText: NO]; targetLongitudeLabel.text = [[LocatorModel sharedInstance] targetLongitudeText: NO]; } else { targetLatitudeLabel.text = kEmptyText; targetLongitudeLabel.text = kEmptyText; } currentLatitudeLabel.text = [[LocatorModel sharedInstance] currentLatitudeText: NO]; currentLongitudeLabel.text = [[LocatorModel sharedInstance] currentLongitudeText: NO]; // update the view [self updateCompassAndArrowBearing:newTarget updateCompass:compassUpdate]; } // update the bearing. - (void) updateCompassAndArrowBearing:(BOOL)newTarget updateCompass:(BOOL)compassUpdate { LocatorModel* ins = [LocatorModel sharedInstance]; Calculations* calc = [[[Calculations alloc] init] autorelease]; [calc initSourceCoords:ins.currentLocation.coordinate.latitude longitude:ins.currentLocation.coordinate.longitude]; [calc initDestinationCoords:ins.targetLocation.coordinate.latitude longitude:ins.targetLocation.coordinate.longitude]; // calc the current bearing for the compass CLLocationDirection arrowBearing = [calc calcBearing]; CLLocationDirection compassHeading = 0.0; CLLocationAccuracy currentAccuracy = ins.currentLocation.horizontalAccuracy; CLLocationAccuracy previousAccuracy = ins.previousLocation.horizontalAccuracy; BOOL moveCompass = [[NSUserDefaults standardUserDefaults] boolForKey:kCompassRotation]; BOOL animate = NO; BOOL newCheck = [ins currentHeading] > 0.0 && [CLLocationManager headingAvailable]; @synchronized(self) { if (newCheck) { compassHeading = [ins currentHeading]; // shift by 10 to keep 1 decimal place of accuracy. long int numToMod = ((arrowBearing + compassHeading) * 10.0) + 3600; arrowBearing = (numToMod % 3600) / 10.0; // move the arrow with the compass. animate = YES; } // else if we don't have a heading, try to calculate it manually. The old long way. else if (ins.previousLocation != nil && [ins getAccuracyGroup:currentAccuracy] == [ins getAccuracyGroup:previousAccuracy] && currentAccuracy > 0.0 && previousAccuracy > 0.0) { CLLocationDistance howFar = [ins.currentLocation distanceFromLocation:ins.previousLocation]; CLLocationAccuracy accuracyDistanceChange = kCompassAccuracy; // if the distance is < 100 meters update more frequently. if (ins.distanceToTarget < 100.0) { accuracyDistanceChange = 0.0; } // otherwise calculate actual accuracy change. else if (ins.currentLocation.horizontalAccuracy > 0.0 && ins.previousLocation.horizontalAccuracy > 0.0) { accuracyDistanceChange = fabsf(ins.currentLocation.horizontalAccuracy - ins.previousLocation.horizontalAccuracy); } BOOL oldCheck = (howFar > accuracyDistanceChange && howFar > kCompassAccuracy && (currentAccuracy <= previousAccuracy || currentAccuracy <= kCLLocationAccuracyHundredMeters)); if (oldCheck && YES == moveCompass && YES == compassUpdate) { // calc the bearing between the old and new points for the compass double tempBearing = [calc calcBearingWithLatitude:ins.previousTimerLocation.coordinate.latitude latitude:ins.currentLocation.coordinate.latitude longitude:ins.previousTimerLocation.coordinate.longitude longitude:ins.currentLocation.coordinate.longitude]; // take the current movement bearing and move the compass accordingly. compassHeading = 360.0 - tempBearing; if (prevBearing > 0.0) { // only make course corrections every x degrees if (fabsf(prevBearing - compassHeading) < kCompassDegreeAccuracy && newCheck == NO) compassHeading = prevBearing; } arrowBearing = (int)(arrowBearing + compassHeading + 360) % 360; // move the arrow with the compass. animate = YES; prevBearing = compassHeading; } } CGAffineTransform transform; if (animate == YES) { // rotate the arrow [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:ARROW_ANIMATION_DURATION_SECONDS]; [UIView setAnimationDelay:0.0]; transform = CGAffineTransformMakeRotation([calc toRadian:arrowBearing]); compassArrow.transform = transform; [UIView commitAnimations]; // rotate the compass [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:ARROW_ANIMATION_DURATION_SECONDS]; [UIView setAnimationDelay:0.0]; transform = CGAffineTransformMakeRotation([calc toRadian:compassHeading]); compass.transform = transform; [UIView commitAnimations]; // only update the bearing text when the compass and arrow rotate. bearingLabel.text = [NSString stringWithFormat:@"%.0f°", [calc calcBearing]]; } else if (moveCompass == NO || newTarget) // rotate the arrow without moving the compass { // rotate the arrow [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:ARROW_ANIMATION_DURATION_SECONDS]; [UIView setAnimationDelay:0.0]; transform = CGAffineTransformMakeRotation([calc toRadian:arrowBearing]); compassArrow.transform = transform; [UIView commitAnimations]; // only update the bearing text when the compass and arrow rotate. bearingLabel.text = [NSString stringWithFormat:@"%.0f°", [calc calcBearing]]; } // update the bearing and accuracy bearingLabel.font = [UIFont systemFontOfSize:13]; accuracyLabel.text = [NSString stringWithFormat:@"±%@", [ins accuracyString]]; accuracyLabel.font = [UIFont systemFontOfSize:13]; } } // turn on the chosen skin. - (void) implementSkins:(NSInteger)skinType { viewSkin = skinType; NSString* path = nil; NSBundle* bundle = [NSBundle mainBundle]; // setup the image path based on the skin type. switch(skinType) { default: case 1: // default, do nothing with the images currentLatitudeLabel.font = [UIFont systemFontOfSize:14]; currentLongitudeLabel.font = [UIFont systemFontOfSize:14]; targetLatitudeLabel.font = [UIFont systemFontOfSize:20]; targetLatitudeLabel.textAlignment = UITextAlignmentCenter; targetLongitudeLabel.font = [UIFont systemFontOfSize:20]; targetLongitudeLabel.textAlignment = UITextAlignmentCenter; break; case 2: // modern look (black & white) path = [bundle pathForResource:kModernArrow ofType:kPNGExtension]; [compassArrow setImage:[UIImage imageWithContentsOfFile:path]]; path = [bundle pathForResource:kModernCompass ofType:kPNGExtension]; [compass setImage:[UIImage imageWithContentsOfFile:path]]; break; case 3: // copper look ("pirate" map) path = [bundle pathForResource:kCopperCompass ofType:kPNGExtension]; [compass setImage:[UIImage imageWithContentsOfFile:path]]; path = [bundle pathForResource:kCopperArrow ofType:kPNGExtension]; [compassArrow setImage:[UIImage imageWithContentsOfFile:path]]; break; case 4: path = [bundle pathForResource:kGreenCompass ofType:kPNGExtension]; [compass setImage:[UIImage imageWithContentsOfFile:path]]; path = [bundle pathForResource:kGreenArrow ofType:kPNGExtension]; [compassArrow setImage:[UIImage imageWithContentsOfFile:path]]; break; case 5: path = [bundle pathForResource:kOutdoorCompass ofType:kPNGExtension]; [compass setImage:[UIImage imageWithContentsOfFile:path]]; path = [bundle pathForResource:kOutdoorArrow ofType:kPNGExtension]; [compassArrow setImage:[UIImage imageWithContentsOfFile:path]]; break; } int yAdjust = 0; int fontAdjust = 0; UIFont* font; UIFont* bigFont; UIColor* color; // now move fields around as needed. switch (skinType) { case 1: [self moveLabel:distanceToTarget withOffsetX:0 andOffsetY:-8]; [self moveLabel:accuracyLabel withOffsetX:-220 andOffsetY:-10]; [self moveLabel:bearingLabel withOffsetX:15 andOffsetY:-11]; accuracyLabel.font = [UIFont systemFontOfSize:13]; bearingLabel.font = [UIFont systemFontOfSize:13]; [self moveLabel:targetLatitudeLabel withOffsetX:0 andOffsetY:-5]; [self moveLabel:targetLongitudeLabel withOffsetX:0 andOffsetY:-10]; [self moveLabel:currentLatitudeLabel withOffsetX:5 andOffsetY:0]; [self moveLabel:currentLongitudeLabel withOffsetX:5 andOffsetY:-5]; break; case 2: case 3: [currentCoordsLabel setHidden:YES]; [targetCoordsLabel setHidden:YES]; [distanceLabel setHidden:YES]; if (skinType == 3) { yAdjust += 19; fontAdjust = -5; } if (skinType == 3) { color = [UIColor colorWithRed:231.0/255.0 green:225.0/255.0 blue:213.0/255.0 alpha:1.0]; font = [UIFont fontWithName:@"Verdana-Bold" size:11]; } else //if (skinType == 2) { color = [UIColor whiteColor]; font = [UIFont fontWithName:@"TrebuchetMS-Bold" size:14]; } [self moveLabel:targetLatitudeLabel withOffsetX:70 andOffsetY:-10 + yAdjust]; targetLatitudeLabel.font = font; targetLatitudeLabel.textColor = color; [self moveLabel:targetLongitudeLabel withOffsetX:70 andOffsetY:-21 + yAdjust + fontAdjust]; targetLongitudeLabel.font = font; targetLongitudeLabel.textColor = color; [self moveLabel:currentLatitudeLabel withOffsetX:38 andOffsetY:15 + yAdjust]; currentLatitudeLabel.font = font; currentLatitudeLabel.textColor = color; [self moveLabel:currentLongitudeLabel withOffsetX:38 andOffsetY:10 + yAdjust + fontAdjust]; currentLongitudeLabel.font = font; currentLongitudeLabel.textColor = color; if (skinType == 2) [self moveLabel:distanceToTarget withOffsetX:40 andOffsetY:-28]; else [self moveLabel:distanceToTarget withOffsetX:40 andOffsetY:-30]; distanceToTarget.textAlignment = UITextAlignmentCenter; distanceToTarget.font = font; distanceToTarget.textColor = color; accuracyLabel.textColor = color; bearingLabel.textColor = color; break; case 4: color = [UIColor whiteColor]; font = [UIFont fontWithName:@"TrebuchetMS-Bold" size:12]; [self moveLabel:currentCoordsLabel withOffsetX:25 andOffsetY:27]; currentCoordsLabel.font = font; currentCoordsLabel.textColor = color; [self moveLabel:targetCoordsLabel withOffsetX:18 andOffsetY:17]; targetCoordsLabel.minimumFontSize = 0.0; targetCoordsLabel.font = font; targetCoordsLabel.textColor = color; [self moveLabel:targetLatitudeLabel withOffsetX:70 andOffsetY:-1]; targetLatitudeLabel.font = font; targetLatitudeLabel.textColor = color; [self moveLabel:targetLongitudeLabel withOffsetX:70 andOffsetY:-13]; targetLongitudeLabel.font = font; targetLongitudeLabel.textColor = color; [self moveLabel:currentLatitudeLabel withOffsetX:40 andOffsetY:24]; currentLatitudeLabel.font = font; currentLatitudeLabel.textColor = color; [self moveLabel:currentLongitudeLabel withOffsetX:40 andOffsetY:17]; currentLongitudeLabel.font = font; currentLongitudeLabel.textColor = color; [self moveLabel:distanceLabel withOffsetX:90 andOffsetY:-15]; distanceLabel.font = font; distanceLabel.textColor = color; [self moveLabel:distanceToTarget withOffsetX:40.0 andOffsetY:-28]; distanceToTarget.textAlignment = UITextAlignmentCenter; distanceToTarget.font = font; distanceToTarget.textColor = color; accuracyLabel.textColor = color; bearingLabel.textColor = color; break; case 5: color = [UIColor whiteColor]; font = [UIFont fontWithName:@"TrebuchetMS-Bold" size:12]; bigFont = [UIFont fontWithName:@"AppleGothic" size:22]; [self moveLabel:currentCoordsLabel withOffsetX:22 andOffsetY:10]; currentCoordsLabel.minimumFontSize = 0.0; currentCoordsLabel.font = font; currentCoordsLabel.textColor = color; [self moveLabel:targetCoordsLabel withOffsetX:18 andOffsetY:-2]; targetCoordsLabel.minimumFontSize = 0.0; targetCoordsLabel.font = font; targetCoordsLabel.textColor = color; [self moveLabel:targetLatitudeLabel withOffsetX:50 andOffsetY:-5]; targetLatitudeLabel.minimumFontSize = 0.0; targetLatitudeLabel.font = bigFont; targetLatitudeLabel.textColor = color; [self moveLabel:targetLongitudeLabel withOffsetX:50 andOffsetY:-5]; targetLongitudeLabel.minimumFontSize = 0.0; targetLongitudeLabel.font = bigFont; targetLongitudeLabel.textColor = color; [self moveLabel:currentLatitudeLabel withOffsetX:10 andOffsetY:12]; currentLatitudeLabel.minimumFontSize = 0.0; currentLatitudeLabel.font = bigFont; currentLatitudeLabel.textColor = color; currentLatitudeLabel.frame = CGRectMake(currentLatitudeLabel.frame.origin.x, currentLatitudeLabel.frame.origin.y, currentLatitudeLabel.frame.size.width + 25, currentLatitudeLabel.frame.size.height + 15); [self moveLabel:currentLongitudeLabel withOffsetX:10 andOffsetY:17]; currentLongitudeLabel.minimumFontSize = 0.0; currentLongitudeLabel.font = bigFont; currentLongitudeLabel.textColor = color; currentLongitudeLabel.frame = CGRectMake(currentLongitudeLabel.frame.origin.x, currentLongitudeLabel.frame.origin.y, currentLongitudeLabel.frame.size.width + 25, currentLongitudeLabel.frame.size.height + 15); [self moveLabel:distanceLabel withOffsetX:190 andOffsetY:-45]; distanceLabel.text = @"Distance"; distanceLabel.font = bigFont; distanceLabel.textColor = color; distanceLabel.frame = CGRectMake(distanceLabel.frame.origin.x, distanceLabel.frame.origin.y, distanceLabel.frame.size.width - 25, distanceLabel.frame.size.height + 15); [self moveLabel:distanceToTarget withOffsetX:130 andOffsetY:-41]; distanceToTarget.textAlignment = UITextAlignmentCenter; distanceToTarget.font = bigFont; distanceToTarget.textColor = color; accuracyLabel.textColor = color; bearingLabel.textColor = color; break; default: // do nothing break; } } - (id)initWithFrame:(CGRect)frame { if ( (self = [super initWithFrame:frame]) ) { // Initialization code } return self; } - (void)drawRect:(CGRect)rect { // Drawing code } // Allocates and initializes a compass view object. // Sets characteristics to default values. // -(UIImageView *)newCompassViewWithImageNamed:(NSString *)imageName atPostion:(CGPoint)centerPoint { UIImage *image = [UIImage imageNamed:imageName]; UIImageView *theView = [[UIImageView alloc] initWithImage:image]; // Set the center of the view. theView.center = centerPoint; theView.alpha = 1; theView.contentMode = UIViewContentModeCenter; // Disable user interaction for this view. You must do this if you want to handle touches for more than one object at at time. // You'll get events for the superview, and then dispatch them to the appropriate subview in the touch handling methods. theView.userInteractionEnabled = NO; return theView; } // Allocates and initializes an arrow view object. // Sets characteristics to default values. // -(UIImageView *)newArrowViewWithImageNamed:(NSString *)imageName atPostion:(CGPoint)centerPoint { UIImage *image = [UIImage imageNamed:imageName]; UIImageView *theView = [[UIImageView alloc] initWithImage:image]; // Set the center of the view. theView.center = centerPoint; // Set alpha so it is slightly transparent to allow seeing pieces move over each other. theView.alpha = 0.05; theView.opaque = NO; // Disable user interaction for this view. You must do this if you want to handle touches for more than one object at at time. // You'll get events for the superview, and then dispatch them to the appropriate subview in the touch handling methods. theView.userInteractionEnabled = NO; return theView; } - (void)dealloc { // Release each of the subviews [compass release]; [compassArrow release]; // Release the labels [distanceToTarget release]; [super dealloc]; } @end