Ios 平滑移动大头针视图 (类似滴滴和 uber)

Posted by ZeroJian on August 12, 2016

iOS 平滑移动大头针视图(类似滴滴和UBER)

大家都知道,地图上的大头针视图如果想平滑的从一个坐标点移动到另一个坐标点,重新添加一个大头针是无法实现这种效果的,那样不会有一个移动效果,如果想达到类似滴滴车辆平滑移动效果,就必须在同一个大头针对象中改变它的坐标,大头针的视图方向也要根据新的角度改变他的视图角度.

我们这里新建一个 LocationManager 类用来获取用户的坐标,当然如果业务需求是服务器获取,实现思路类似,只是改变一下坐标点的获取方式.具体 LocationManager 单例的创建细节就不多说了,我们在获取到用户位置的回调里面添加一个通知

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
	CLLocation *location = locations.firstObject;
	[[NSNotificationCenter defaultCenter] postNotificationName:kdidUpdateLocation object:location];
}

主要说说自定义大头针,大头针分 MKAnnotationView 用来显示大头针视图, 和 MKAnnotation 用来设置大头针的一些属性,可以简单的把它看成一个 view - model 的关系

如果需要自定义大头针, 需要遵守 BKAnnotation 协议,其中 coordinate 属性是必须实现的属性

@protocol MKAnnotation <NSObject>

// Center latitude and longitude of the annotation view.
// The implementation of this property must be KVO compliant.
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;

@optional

// Title and subtitle for use by selection UI.
@property (nonatomic, readonly, copy, nullable) NSString *title;
@property (nonatomic, readonly, copy, nullable) NSString *subtitle;

// Called as a result of dragging an annotation view.
- (void)setCoordinate:(CLLocationCoordinate2D)newCoordinate NS_AVAILABLE(10_9, 4_0);

@end

在自定义的大头针里面我们添加几个自定义属性

@property (nonatomic, assign) MyAnnotationType type;

@property (nonatomic, assign) double angle;

@property (nonatomic, strong) MKAnnotationView *annotationView;

其中 type 是一个枚举类型,用来自定义大头针的类型, angle 表示方向,用来显示大头针的方向

在 .m 文件中自定义几个内部属性

	NSMutableArray *locations;
	NSArray *moveArray;
	BOOL movingOver;
	NSInteger currentIndex;
	UIImage *annnationImage;

在大头针的初始化方法中,监听定位通知,初始化保存定位的数组

- (instancetype)initWithType:(MyAnnotationType)type
				  coordinate:(CLLocationCoordinate2D)coordinate
					   angle:(double)angle {
  
	if (self == [super init]) {

		_type = type;
		_coordinate = coordinate;
		_angle = angle;
		if (type == MyAnnotationTypeCar) {
			[[NSNotificationCenter defaultCenter] addObserver:self	 			     selector:@selector(onNotifiedCurrentLocationUpdated:) 							   name:kdidUpdateLocation
						 object:nil];
			locations = [[NSMutableArray alloc] initWithCapacity:20];
			movingOver = YES;
		}
	}
	return self;
}

在监听的通知中, 我们保存获取到的定位对象,为了保证大头针的移动效果,我们需要在建一个移动的数组,这个移动数组保证有一定量的定位数据才开始移动动画效果,而定位数组只负责把所有获取到的定位对象保存下来

- (void)onNotifiedCurrentLocationUpdated:(NSNotification *)notification
{
	if (notification.object) {
			CLLocation *location = notification.object;
			[locations addObject:location];
			
			if (locations.count >= 5 && movingOver == YES) {
					moveArray = [NSArray arrayWithArray:locations];
					[locations removeAllObjects];
					NSLog(@"----- moveArrayCount: %ld",moveArray.count);
					NSLog(@"------beging moving -----");
					currentIndex = 0;
					movingOver = NO;
					[self startMoving];
			}
	}
}	

核心的移动大头针方法,使用递归的方式一次次的移动大头针,在移动数组全部使用后结束递归,等待新获取到的数组

- (void)startMoving
   	{
	NSInteger index = currentIndex % moveArray.count;
	CLLocation *newLocation = moveArray[index];
	
	self.annotationView.image = [annnationImage zj_imageRotatedByAngle:newLocation.course];
	
	CLLocation *currentLocation = [[CLLocation alloc] initWithLatitude:self.coordinate.latitude longitude:self.coordinate.longitude];
	double distance = [newLocation distanceFromLocation:currentLocation];
	
	double speed = newLocation.speed;
	
	CLLocationCoordinate2D newCoordinate = newLocation.coordinate;
	
	[UIView animateWithDuration:distance/speed animations:^{
			self.coordinate = newCoordinate;
			currentIndex ++;
	} completion:^(BOOL finished) {
			if (currentIndex == moveArray.count) {
					movingOver = YES;
					moveArray = nil;
			} else {
					[self startMoving];
			}
	}];
}

只能需要注意的几个细节,一个是大头针角度,在 CLLocation 对象里面是有一个角度属性和速度属性的,当然如果只有两个坐标点也是可以通过算法获取到的.

这里要用到的速度和两个坐标点的距离用来设定车辆移动动画的时间,这样可以保证车辆的移动速度是均匀的平滑的.

我们在每次动画后把创建的 index + 1 ,然后和数组的 count 通过取余运算获取到下次递归数组使用的 index

大头针的平滑移动,只需使用 UIView 动画简单的把新的坐标赋值给大头针即可实现.

项目 DEMO : https://github.com/ZeroJian/SmoothMovingAnnotation

有帮助给个 start 吧!