달성하려는 최종 상태는 "차량"annotations
컬렉션을 새로 고치는 타이머를 실행하는 것입니다. annotation
coordinates
은 Timer를 사용하여 60 초마다 성공적으로 새로 고침되지만 사용자는 mapView : regionWillChangeAnimated
및 mapView : regionWillChangeAnimated
대리자를 호출해야합니다. 이 대리인은 올바르게 작동하고 차량 annotations
을 움직이고 있지만 사용자가 화면과 상호 작용하지 않고 자율적으로 이동하려고합니다.MapKit - MapView를 업데이트하여 타이머를 통해 움직이는 주석을 표시합니다.
내 접근 방식은 다음과 같습니다.
1) 타이머를 시작합니다.이 기능은 완벽하게 작동합니다!
//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
#pragma mark Timers
//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
dispatch_source_t CreateDispatchTimer(uint64_t interval,
uint64_t leeway, dispatch_queue_t queue, dispatch_block_t block)
{
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (timer)
{
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, interval), interval * NSEC_PER_SEC, (1ull * NSEC_PER_SEC)/10);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
- (void)startTimer
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
double secondsToFire = 60.000f;
double secondsLeeway = 2.000f;
_timer = CreateDispatchTimer(secondsToFire, secondsLeeway, queue, ^{
// Do something
//Method Call to Refresh NSMutableArray of Vehicle Models
[self RefreshVehicles];
NSLog(@"TIMER TASK CALLED");
});
}
- (void)cancelTimer
{
if (_timer) {
dispatch_source_cancel(_timer);
_timer = nil;
}
}
타이머 (void)RefreshVehicles
을 호출하여 페치 및 NSMutable Array
으로 최근 자동차를로드하는 데 사용되며,이 차량 annotation
를 업데이트하는 데 사용되는 각각의 오브젝트에 대한 최신 coordinates
를 업데이트한다. 비동기 네트워크 통화와 SQLite
이 차량 기록을 업데이트하는 작업을 완료하면 NSNotification
을 사용하여 알려드립니다. 알림 화재, 나는 기존 차량 annotations
을 제거하고있어 다음지도에 새로운 annotations
을 추가 addVehiclesToMap
를 호출하여, 현지 차량 NSMutable Array
를 업데이트 할 때 :
//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
#pragma mark Notification Listener
//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
- (void)RefreshVehicles:(NSNotification *)notif {
NSLog(@"++++++++++++++++++++++++++++++++++++++++ Vehicles UPDATED!");
** POST SOLUTION REMARK: MOVED REMOVE ANNOTATION LOGIC TO: (void)addVehiclesToMap
//** MOVED //If it already Exists, Remove it, Must redraw vehicles because they are moving.
//** MOVED for (id <MKAnnotation> annotation in self.mapView.annotations)
//** MOVED{
//** MOVED//Only Remove Vehicles, Leave Stations, they are static
//** MOVED if ([annotation isKindOfClass:[VehicleAnnotation class]])
//** MOVED{
//** MOVED [self.mapView removeAnnotation:annotation];
//** MOVED}
//** MOVED}
//Load Vehicle Collection
self.vehicleCollection = [[VehicleModelDataController defaultVehicleModelDataController] vehicleReturnAll];
[self addVehiclesToMap];
}
여기 addVehiclesToMap
의 방법입니다 : ** POST 해결 방법 비 고 : *** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSArrayM: 0x16d94af0> was mutated while being enumerated.
'
나는 백그라운드 스레드에서 타이머 새로 고침에서 주석을 제거했기 때문에 이것은 다음 Main Thread
에지도 annotations
를 업데이트 안나의 솔루션을 구현 한 후, 나는 다음과 같은 오류가 발생하기 시작했다. 이 문제를 해결하려면, 나뿐만 아니라 메인 스레드에 [self.mapView removeAnnotation:annotation];
을 구현 **
/*
----- VEHICLES -----
*/
- (void)addVehiclesToMap {
//If it already Exists, Remove it, Must redraw vehicles because they are moving.
for (id <MKAnnotation> annotation in self.mapView.annotations)
{
//Only Remove Vehicles, Leave Stations, they are static
if ([annotation isKindOfClass:[VehicleAnnotation class]])
{
//Remove Vehicle Annotation to MapView on the Main Thread
dispatch_async(dispatch_get_main_queue(), ^{
[self.mapView removeAnnotation:annotation];
});
}
}
//Loop through Vehicle Collection and generate annotation for each Vehicle Object
for (VehicleModel *vehicle in vehicleCollection) {
//New Vehicle Annotation Instance
VehicleAnnotation *myVehicleAnnotation = [[VehicleAnnotation alloc] init];
myVehicleAnnotation.coordinate = CLLocationCoordinate2DMake([vehicle.vehicleLat doubleValue], [vehicle.vehicleLon doubleValue]);
myVehicleAnnotation.vehicleId = [vehicle.vehicleId stringValue];
myVehicleAnnotation.title = vehicle.vehicleLabel;
myVehicleAnnotation.subtitle = vehicle.vehicleIsTrainDelayed;
**POST SOLUTION REMARK: PER ANNA'S SOLUTION, MOVE addAnnodation TO MAIN THREAD:**
//MODIFIED THIS:** [self.mapView addAnnotation:myVehicleAnnotation];
**//TO THIS:**
//Add Vehicle Annotation to MapView on the Main Thread
dispatch_async(dispatch_get_main_queue(), ^{
[self.mapView addAnnotation:myVehicleAnnotation];
});**
}
}
다음 viewAnnotation
위임에 대한 코드 :.
//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
#pragma mark MKAnnotationView Delegate
//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation
{
// if it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
// handle our two custom annotations
//
if ([annotation isKindOfClass:[VehicleAnnotation class]]) /// for Vehicles Only
{
//Important, can't use annotation, this lets the compiler know that the annotation is actually an StationAnnotation object.
VehicleAnnotation *vehicleAnnotation = (VehicleAnnotation *)annotation;
//Reuse existing Annotation
NSString* AnnotationIdentifier = vehicleAnnotation.vehicleId.lowercaseString;
MKPinAnnotationView* pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationIdentifier];
if (!pinView)
{
//Set unique annotation identifier exp: 304 (The Vehicles's Unique Number)
NSString* AnnotationIdentifier = vehicleAnnotation.vehicleId.lowercaseString;
MKAnnotationView *annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier];
annotationView.canShowCallout = YES;
NSString *vehicleFlagIcon = [@"map_train_green_" stringByAppendingString:vehicleAnnotation.vehicleId.lowercaseString];
UIImage *flagImage = [UIImage imageNamed:vehicleFlagIcon];
CGRect resizeRect;
resizeRect.size = flagImage.size;
CGSize maxSize = CGRectInset(self.view.bounds,
[VehicleMapViewController annotationPadding],
[VehicleMapViewController calloutHeight]).size;
maxSize.height -= self.navigationController.navigationBar.frame.size.height + [VehicleMapViewController calloutHeight];
if (resizeRect.size.width > maxSize.width)
resizeRect.size = CGSizeMake(maxSize.width, resizeRect.size.height/resizeRect.size.width * maxSize.width);
if (resizeRect.size.height > maxSize.height)
resizeRect.size = CGSizeMake(resizeRect.size.width/resizeRect.size.height * maxSize.height, maxSize.height);
resizeRect.origin = (CGPoint){0.0f, 0.0f};
UIGraphicsBeginImageContext(resizeRect.size);
[flagImage drawInRect:resizeRect];
UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
annotationView.image = resizedImage;
annotationView.opaque = NO;
NSString *vehicleLogo = [@"map_train_green_" stringByAppendingString:vehicleAnnotation.vehicleId.lowercaseString];
UIImageView *sfIconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:vehicleLogo]];
annotationView.leftCalloutAccessoryView = sfIconView;
return annotationView;
}
else
{
pinView.annotation = annotation;
}
return pinView;
}
return nil;
}
다음은 내가 제거 논리가 할 다음 대리자를 사용하여 annotations
을 다시 추가하십시오. 이러한 대리자는 정상적으로 작동하지만 사용자가 화면과 상호 작용해야합니다. 지도를 X 초마다 새로 고치려고합니다. 지금까지 annotation
위치의 변경 사항을 보려면 화면을 터치하고지도를 이동해야 이러한 삭제 항목을 호출해야합니다. 나는 아직도 상호 작용을 수행 할 필요없이 차량이 자율적으로 움직이는 것을 볼 수 없다.
POST 솔루션은 비고 : 그들은 MAP 터치 및 이동 된하시면 아이콘 플리커를 만드는 차량 주석에게 UN-필요한 업데이트를 수행하고 있었기 때문에 나는이 대의원 제거 ... 타이머가 작업을 할셔서
**// DELETED** -(void)mapView:(MKMapView *)theMapView regionWillChangeAnimated:(BOOL)animated { ...Annotation tear down and rebuild code here }
**//DELETED** -(void)mapView:(MKMapView *)theMapView didUpdateUserLocation:(MKUserLocation *)userLocation { ...Annotation tear down and rebuild code here }
여기에 해결책이 있습니까? 미리 감사드립니다 ...
+ 1 애나! 고마워, 나는 제안 된 변경 사항을 반영하기 위해 내 질문을 업데이트했다. 또한 움직이기 [self.mapView removeAnnotation : annotation]; 불쾌한 변형 된 Array 예외를 피하기 위해 주 스레드에. 이것은 훌륭한 출발점이며 실제 및 과거 위치를 추적 할 수 있도록 2 세트의 차량 컬렉션을 유지 한 다음 주석을 자르고 다시 작성하는 대신 업데이트/제거/추가하는 아이디어와 같습니다. 그러나이 솔루션은 최적이며지도가 반응 형이며 UI가 상호 작용으로 활공하므로 솔루션을 리팩토링하지 않아도됩니다. – CampbellGolf
업데이트 : 주석에 ADD/DELETE/UPDATE를 확인하기 위해 addVehiclesToMap을 다시 작성했습니다. Annotation이 삭제되고 추가 될 때 주된 이유는 주석의 annotationView.canShowCallout = YES가 문제가되었다는 것입니다. 사용자가 주석 호출을 표시 한 경우 주석 코드를 삭제하거나 다시 추가하면 사라질 것입니다 ... 훌륭한 사용자 경험 IMO가 아닙니다. – CampbellGolf