2014-01-30 10 views
2

달성하려는 최종 상태는 "차량"annotations 컬렉션을 새로 고치는 타이머를 실행하는 것입니다. annotationcoordinates은 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 } 

여기에 해결책이 있습니까? 미리 감사드립니다 ...

답변

4

메인 스레드에서 addAnnotation를 호출 시도 그래서 지체없이 UI 업데이트 :

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



관련이없는 제안 :
대신 제거하고 다시 추가 차량 주석을 기존 차량의 coordinate 속성을 간단히 업데이트 할 수 있으며지도보기는 자동으로 주석보기를 이동합니다. 논리를 구현하는 것이 약간 힘들지만 (예 :지도보기 annotations 배열의 기존 차량 주석을 찾아 새로운 VehicleAnnotation 대신에 업데이트해야 할 필요가 있음) 약간 더 매끄러운 결과가 발생할 수 있습니다. 더 이상 존재하지 않는 차량에 대한 새로운 차량을 설명하고 주석을 삭제해야합니다.


또 다른 관련이없는 제안이 원형 교차로의 대신
설정 할 비밀 방법은 좌표

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]); 
+0

+ 1 애나! 고마워, 나는 제안 된 변경 사항을 반영하기 위해 내 질문을 업데이트했다. 또한 움직이기 [self.mapView removeAnnotation : annotation]; 불쾌한 변형 된 Array 예외를 피하기 위해 주 스레드에. 이것은 훌륭한 출발점이며 실제 및 과거 위치를 추적 할 수 있도록 2 세트의 차량 컬렉션을 유지 한 다음 주석을 자르고 다시 작성하는 대신 업데이트/제거/추가하는 아이디어와 같습니다. 그러나이 솔루션은 최적이며지도가 반응 형이며 UI가 상호 작용으로 활공하므로 솔루션을 리팩토링하지 않아도됩니다. – CampbellGolf

+1

업데이트 : 주석에 ADD/DELETE/UPDATE를 확인하기 위해 addVehiclesToMap을 다시 작성했습니다. Annotation이 삭제되고 추가 될 때 주된 이유는 주석의 annotationView.canShowCallout = YES가 문제가되었다는 것입니다. 사용자가 주석 호출을 표시 한 경우 주석 코드를 삭제하거나 다시 추가하면 사라질 것입니다 ... 훌륭한 사용자 경험 IMO가 아닙니다. – CampbellGolf