Конечное состояние, которое я пытаюсь достичь, - это запустить таймер, который обновляет коллекцию «Автомобиль» annotations
. annotation
coordinates
успешно обновляется каждые 60 секунд с использованием таймера, но пользователь должен вызвать mapView: regionWillChangeAnimated
и mapView: regionWillChangeAnimated
делегатов. Эти делегаты правильно работают и перемещают Транспортное средство annotations
, но я хочу, чтобы 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;
}
}
Таймер используется для извлечения и загрузки последних кораблей в NSMutable Array
по телефону (void)RefreshVehicles
, это будет обновлять последнюю coordinates
для каждого объекта, который будет использоваться для обновления транспортного средства annotation
. Я использую NSNotification
, чтобы узнать, когда работа в сети Async Network Call и SQLite
завершена, чтобы обновить записи транспортного средства. Когда уведомление срабатывает, Я удаляю все существующие Vehicle annotations
, а затем обновить локальную Vehicle NSMutable Array
, позвонив по телефону addVehiclesToMap
добавить новый annotations
к карте:
//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
#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 рЕШЕНИЕ Замечание: После внедрения решения Анны обновить карту annotations
на Main Thread
, я начал получать следующее сообщение об ошибке: *** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSArrayM: 0x16d94af0> was mutated while being enumerated.
'
Это потому, что я удалял аннотации от обновления таймера в фоновом потоке. Чтобы устранить эту проблему, я осуществил [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 РЕШЕНИЯ СЛОВА: Я устранил эти ДЕЛЕГАТ, потому что они ВЫПОЛНЕНИЕ ООН необходимых изменений АВТОМОБИЛЯ АННОТАЦИИ СОЗДАНИЯ значков фликера когда карта была тронута и Перенесена ... Позволить ТАЙМЕР ДЕЛАТЬ РАБОТУ
**// 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. Это отличный стартер, и, как идея поддержания двух наборов коллекций автомобилей, отслеживать фактические и прошлые местоположения, а затем обновлять/удалять/добавлять вместо того, чтобы срывать и восстанавливать аннотации. Однако это решение является оптимальным, карта реагирует и пользовательский интерфейс скользит с взаимодействием, поэтому мне, возможно, не придется реорганизовывать решение. – CampbellGolf
ОБНОВЛЕНИЕ: Я закончил переписывать addVehiclesToMap, чтобы проверить ADD/DELETE/UPDATE для аннотаций. Основная причина - аннотация annotationView.canShowCallout = YES стала проблемой, когда аннотация была удалена и добавлена. Если пользователь вывел вызов аннотации, он исчезнет, когда координация аннотации будет удалена/добавлена повторно ... не отличная пользовательская IMO. – CampbellGolf