ios - MapKit - 更新 MapView 以通过计时器显示移动注释

标签 ios objective-c mkmapview mapkit mkannotationview

我试图实现的最终状态是有一个正在运行的计时器来刷新“车辆”集合 annotationsannotation coordinates使用计时器每 60 秒成功刷新一次,但用户必须调用 mapView:regionWillChangeAnimated和 map View :regionWillChangeAnimated代表们。这些代表正在正确工作和移动车辆 annotations ,但我想要annotations无需用户与屏幕交互即可自主移动。

这是我的方法:

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了解何时异步网络调用和 SQLite更新车辆记录的工作已完成。当通知触发时,我将删除任何现有车辆 annotations然后更新本地车辆NSMutable Array ,调用addVehiclesToMap添加新的annotations到 map :

//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
#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 的方法: **发布解决方案备注:实现安娜的解决方案更新 map 后 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 秒刷新一次 map 。到目前为止看到annotation的任何变化位置,我必须触摸屏幕并移动 map 才能调用这些删除。我仍然无法看到车辆在无需执行交互的情况下自动移动。

发布解决方案备注:我删除了这些代表,因为他们正在对车辆注释执行不必要的更新,导致触摸和移动 map 时图标闪烁...让计时器来完成工作

**// 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 }

我已经接近解决方案了吗?提前致谢...

最佳答案

尝试在主线程上调用 addAnnotation,以便 UI 更新不会出现延迟:

dispatch_async(dispatch_get_main_queue(), ^{
    [self.mapView addAnnotation:myVehicleAnnotation];
});



不相关的建议:
您无需删除并重新添加车辆注释,只需更新现有车辆的坐标属性, map View 就会自动移动注释 View 。这可能会导致稍微平滑的效果,尽管实现逻辑稍微困难一些(例如,您需要在 map View annotations 数组中找到现有的车辆注释并更新它,而不是创建新的车辆注释)。您还必须考虑新车辆并删除不再存在的车辆的注释。


另一个不相关的建议:
而不是这种迂回和神秘的方式来设置坐标:

NSString *coord = [NSString stringWithFormat:@"{%@,%@}", 
   [NSString stringWithFormat:@"%@", vehicle.vehicleLat], 
   [NSString stringWithFormat:@"%@", vehicle.vehicleLon]];
CGPoint point = CGPointFromString(coord);
myVehicleAnnotation.coordinate = CLLocationCoordinate2DMake(point.x, point.y);  

我建议采用这种更直接、更不那么神秘的方法:

myVehicleAnnotation.coordinate = CLLocationCoordinate2DMake(
    [vehicle.vehicleLat doubleValue], [vehicle.vehicleLon doubleValue]);

关于ios - MapKit - 更新 MapView 以通过计时器显示移动注释,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21446917/

相关文章:

ios - 已加载的 UIView 元素的更改

ios - View Controller 应该如何中止加载/关闭自身?

ios - 代号一 - 自定义 TextField

ios - 使用单元格中的按钮来增加该特定单元格的值

objective-c - 从 UITextField 获取对 super View 的引用?

iphone - 如何使用 Xcode 制作首次启动 iPhone App 的导览

ios - Sprite 套件中的 UIScrollView

iphone - 仅在叠加层内显示注释

ios - 在 leftCalloutAccessoryView Map Xcode 中显示不同的图像

iOS swift : MKPointAnnotation not always showing title