菜鸟教程小白 发表于 2022-12-13 06:13:44

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


                                            <p><p>我想要达到的最终状态是让一个正在运行的计时器刷新“车辆”<code>annotations</code> 的集合。 <code>annotation</code> <code>coordinates</code> 使用 Timer 每 60 秒成功刷新一次,但用户必须调用 mapView:<code>regionWillChangeAnimated</code> 和 mapView:<code>regionWillChangeAnimated</code> 代表。这些代表正在正确工作并移动 Vehicle <code>annotations</code>,但我希望 <code>annotations</code> 能够自主移动,而无需用户与屏幕进行交互。 </p>

<p>这是我的方法:</p>

<p>1) 启动计时器.. 完美!</p>

<pre><code>//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
#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
    ;
    NSLog(@&#34;TIMER TASK CALLED&#34;);
});
}

- (void)cancelTimer
{
if (_timer) {
    dispatch_source_cancel(_timer);      
    _timer = nil;
    }
}
</code></pre>

<p>计时器用于通过调用 <code>(void)RefreshVehicles</code> 获取最新的 Vehicles 并将其加载到 <code>NSMutable Array</code> 中,这将更新最新的<code>坐标</code> 用于更新 Vehicle <code>annotation</code> 的每个对象。我正在使用 <code>NSNotification</code> 来了解异步网络调用和 <code>SQLite</code> 何时更新车辆记录完成。当通知触发时,我将删除任何现有的 Vehicle <code>annotations</code>,然后通过调用 <code>addVehiclesToMap</code> 来更新本地 Vehicle <code>NSMutable Array</code> 以添加新的<code>注释</code>到 map :</p>

<pre><code>//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
#pragma mark Notification Listener
//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

- (void)RefreshVehicles:(NSNotification *)notif {

NSLog(@&#34;++++++++++++++++++++++++++++++++++++++++Vehicles UPDATED!&#34;);

** 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 &lt;MKAnnotation&gt; annotation in self.mapView.annotations)
//** MOVED{
   //** MOVED//Only Remove Vehicles, Leave Stations, they are static
   //** MOVED if (])
   //** MOVED{
      //** MOVED ;
   //** MOVED}
//** MOVED}

//Load Vehicle Collection
self.vehicleCollection = [ vehicleReturnAll];   

;

}
</code></pre>

<p><code>addVehiclesToMap</code> 的方法如下:
**POST SOLUTION REMARK:在 <code>Main Thread</code> 上实现 Anna 的解决方案以更新 map<code>annotations</code> 后,我开始收到以下错误:<code>*** Terminating app由于未捕获的异常 'NSGenericException',原因:'*** Collection <__NSArrayM: 0x16d94af0> 在枚举时发生了变异。</code>'</p>

<p>这是因为我从后台线程的计时器刷新中删除了注释。为了解决这个问题,我也在主线程中实现了 <code>;</code>。**</p>

<pre><code>/*
----- VEHICLES -----
*/
- (void)addVehiclesToMap {

//If it already Exists, Remove it, Must redraw vehicles because they are moving.
for (id &lt;MKAnnotation&gt; annotation in self.mapView.annotations)
{
    //Only Remove Vehicles, Leave Stations, they are static
    if (])
    {
      //Remove Vehicle Annotation to MapView on the Main Thread
      dispatch_async(dispatch_get_main_queue(), ^{
         ;
      });
    }
}

//Loop through Vehicle Collection and generate annotation for each Vehicle Object
for (VehicleModel *vehicle in vehicleCollection) {

    //New Vehicle Annotation Instance
    VehicleAnnotation *myVehicleAnnotation = [ init];

    myVehicleAnnotation.coordinate = CLLocationCoordinate2DMake(, );
    myVehicleAnnotation.vehicleId = ;               
    myVehicleAnnotation.title = vehicle.vehicleLabel;                              
    myVehicleAnnotation.subtitle = vehicle.vehicleIsTrainDelayed;                  

    **POST SOLUTION REMARK: PER ANNA&#39;S SOLUTION, MOVE addAnnodation TO MAIN THREAD:**
    //MODIFIED THIS:** ;

    **//TO THIS:**
    //Add Vehicle Annotation to MapView on the Main Thread
    dispatch_async(dispatch_get_main_queue(), ^{

      ;
    });**
}
</code></pre>

<p>}</p>

<p>接下来是 <code>viewAnnotation</code> 委托(delegate)的代码:</p>

<pre><code>//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
#pragma mark MKAnnotationView Delegate
//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||


- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id   &lt;MKAnnotation&gt;)annotation
{
    // if it&#39;s the user location, just return nil.
    if (])
    return nil;


// handle our two custom annotations
//
if (]) /// for Vehicles Only
{

   //Important, can&#39;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 *);

    if (!pinView)
    {

      //Set unique annotation identifierexp: 304 (The Vehicles&#39;s Unique Number)
      NSString* AnnotationIdentifier = vehicleAnnotation.vehicleId.lowercaseString;


      MKAnnotationView *annotationView = [ initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier];
      annotationView.canShowCallout = YES;


      NSString *vehicleFlagIcon = [@&#34;map_train_green_&#34; stringByAppendingString:vehicleAnnotation.vehicleId.lowercaseString];
      UIImage *flagImage = ;

      CGRect resizeRect;

      resizeRect.size = flagImage.size;
      CGSize maxSize = CGRectInset(self.view.bounds,
                                     ,
                                     ).size;

      maxSize.height -= self.navigationController.navigationBar.frame.size.height + ;
      if (resizeRect.size.width &gt; maxSize.width)
            resizeRect.size = CGSizeMake(maxSize.width, resizeRect.size.height / resizeRect.size.width * maxSize.width);
      if (resizeRect.size.height &gt; 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);
      ;
      UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
      UIGraphicsEndImageContext();

      annotationView.image = resizedImage;
      annotationView.opaque = NO;

      NSString *vehicleLogo = [@&#34;map_train_green_&#34; stringByAppendingString:vehicleAnnotation.vehicleId.lowercaseString];
      UIImageView *sfIconView = [ initWithImage:];
      annotationView.leftCalloutAccessoryView = sfIconView;

      return annotationView;

    }
    else
    {
      pinView.annotation = annotation;
    }
    return pinView;
}   

return nil;
}
</code></pre>

<p>接下来我确实有逻辑要删除,然后使用以下委托(delegate)重新添加 <code>annotations</code>。这些委托(delegate)工作得很好,但需要用户与屏幕交互。我正在尝试每隔 X 秒刷新一次 map 。到目前为止,要查看对 <code>annotation</code> 位置的任何更改,我必须触摸屏幕并移动 map 以调用这些删除操作。我仍然无法看到车辆自动移动而无需执行交互。</p>

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

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

<p><strong>我是否已经接近解决方案?提前谢谢...</strong> </p></p>
                                    <br><hr><h1><strong>Best Answer-推荐答案</ strong></h1><br>
                                            <p><p>尝试在主线程上调用 <code>addAnnotation</code>,以便 UI 更新不会出现延迟:</p>

<pre><code>dispatch_async(dispatch_get_main_queue(), ^{
    ;
});
</code></pre>

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

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

<pre><code>NSString *coord = [NSString stringWithFormat:@&#34;{%@,%@}&#34;,
   ,
   ];
CGPoint point = CGPointFromString(coord);
myVehicleAnnotation.coordinate = CLLocationCoordinate2DMake(point.x, point.y);
</code></pre>

<p>我建议采用这种更直接、更简洁的方法:</p>

<pre><code>myVehicleAnnotation.coordinate = CLLocationCoordinate2DMake(
    , );
</code></pre></p>
                                   
                                                <p style="font-size: 20px;">关于ios - MapKit - 更新 MapView 以通过计时器显示移动注释,我们在Stack Overflow上找到一个类似的问题:
                                                        <a href="https://stackoverflow.com/questions/21446917/" rel="noreferrer noopener nofollow" style="color: red;">
                                                                https://stackoverflow.com/questions/21446917/
                                                        </a>
                                                </p>
                                       
页: [1]
查看完整版本: ios - MapKit - 更新 MapView 以通过计时器显示移动注释