Ordenar UITableView por distancia

Estoy tratando de orderar mi vista de tabla en order ascendente por la distancia que calculo a partir de coorderadas. Todo funciona como un hechizo, excepto que no puedo conseguirlo en order ascendente, he estado NSSortDescriptor con NSSortDescriptor , etc., pero desafortunado, cualquier ayuda sería apreciada, aquí está mi código:

 - (void) retrieveData { NSURL *url = [NSURL URLWithString:jsonFile]; NSData *data = [NSData dataWithContentsOfURL:url]; _jsonArray = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]; _salesArray = [[NSMutableArray alloc]init]; for (int i = 0; i < _jsonArray.count; i++) { NSString *sID = [[_jsonArray objectAtIndex:i] objectForKey:@"id"]; NSString *sName = [[_jsonArray objectAtIndex:i] objectForKey:@"name"]; NSString *sAddress = [[_jsonArray objectAtIndex:i] objectForKey:@"address"]; NSString *sPostcode = [[_jsonArray objectAtIndex:i] objectForKey:@"postcode"]; __block NSString *distance; CLGeocoder *geocoder = [[CLGeocoder alloc]init]; [geocoder geocodeAddressString:sPostcode completionHandler:^(NSArray *placemarks, NSError *error) { if (error == nil && placemarks.count > 0) { CLPlacemark *placemark = [placemarks objectAtIndex:0]; CLLocation *location = placemark.location; CLLocation *myLocation = self.manager.location; CLLocationDistance miles = [location distanceFromLocation:myLocation]; //this is the variable i want in my convenience init. distance = [NSString stringWithFormat:@"%.1f m", (miles/1609.344)]; } }]; [_salesArray addObject:[[sales alloc] initWithSales:sID andName:sName andAddress:sAddress andPostcode:distance]]; } [_salesArray sortUsingComparator: ^NSComparisonResult(id obj1, id obj2){ sales *p1 = (sales *)obj1; sales *p2 = (sales *)obj2; if (p1.postcode > p2.postcode) { return (NSComparisonResult)NSOrdenetworkingDescending; } if (p1.postcode < p2.postcode) { return (NSComparisonResult)NSOrdenetworkingAscending; } return (NSComparisonResult)NSOrdenetworkingSame; } ]; [self.tableView reloadData]; } 

Hay algunos problemas aquí:

  1. El geocodeAddressString impone algunas limitaciones, como se describe en la documentation:

    Este método envía los datos de location especificados al server de geoencoding de forma asíncrona y retorna. Su bloque de event handling finalización se ejecutará en el hilo principal. Después de iniciar una request de geoencoding hacia adelante, no intente iniciar otra request de geoencoding hacia adelante o hacia atrás.

    Las requestes de geoencoding tienen una tasa limitada para cada aplicación, por lo que hacer demasiadas requestes en un corto período de time puede hacer que algunas de las requestes fallen. Cuando se supera la velocidad máxima, el geocodificador pasa un object de error con el valor kCLErrorNetwork a su manejador de finalización.

    Varias observaciones key aquí:

    • Esto se ejecuta de forma asíncrona (por lo que no puede llamar a geocodeAddressString y usar sus resultados inmediatamente después). Usted debe invocar el trabajo de forma contingente en la geoencoding dentro del bloque de finalización.

    • No debe comenzar la próxima request de geocódigo hasta que se complete la anterior.

    Esto significa que debe geocodificar el primer código postal, dejar que se complete de forma asíncrona (es decir, más adelante), geocodificar el siguiente, dejar que se complete, etc., y solo hacer su orderación y volver a cargar la tabla. Un simple for bucle no es una forma adecuada de hacer esto. Puede escribir un método que haga un único geocódigo e invoque el siguiente geocódigo en el bloque de finalización, o puede usar la subclass NSOperation como lo tengo a continuación.

  2. Le aconsejaría save la distance como un NSNumber . En MVC, la representación de cadena de un decimal es un comportamiento de "vista" y probablemente no forme parte del "model".

    La ventaja de esto es que cuando desea orderar los objects, simplemente puede invocar el método de compare para el NSNumber . Por ejemplo, si salesPersonnel era un NSMutableArray de objects que cada object SalesPerson tiene la propiedad NSNumber llamada distance , podría hacer lo siguiente:

     [self.salesPersonnel sortUsingComparator:^NSComparisonResult(SalesPerson *obj1, SalesPerson *obj2) { return [obj1.distance compare:obj2.distance]; }]; 

    No estaba seguro si sus inputs de sales por transactions reales de ventas o personal de ventas, por lo que pido disculpas si malinterpreté los types de object, pero espero que esto ilustra la idea.


Puedes hacer esto como quieras, pero para mí, cuando quiero ejecutar una serie de tareas asíncronas, pero lo hago secuencialmente, NSOperation subclass NSOperation concurrente que NSOperation a un NSOperationQueue serial.

 NSError *error; NSArray *addressEntries = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; NSAssert(addressEntries, @"unable to parse: %@", error); NSOperationQueue *queue = [[NSOperationQueue alloc] init]; queue.maxConcurrentOperationCount = 1; self.salesPersonnel = [NSMutableArray array]; // define sort operation that will be called when all of the geocode attempts are done NSOperation *sortAndReloadTableOperation = [NSBlockOperation blockOperationWithBlock:^{ [self.salesPersonnel sortUsingComparator:^NSComparisonResult(SalesPerson *obj1, SalesPerson *obj2) { return [obj1.distance compare:obj2.distance]; }]; [self.tableView reloadData]; }]; // create the geocode operations for (NSDictionary *addressEntry in addressEntries) { SalesPerson *salesPerson = [[SalesPerson alloc] initWithSalesId:addressEntry[@"id"] name:addressEntry[@"name"] address:addressEntry[@"address"] postalCode:addressEntry[@"postcode"]]; [self.salesPersonnel addObject:salesPerson]; NSOperation *geocodeOperation = [[GeocodeOperation alloc] initWithPostalCode:salesPerson.postalCode completionHandler:^(NSArray *placemarks, NSError *error) { CLPlacemark *placemark = [placemarks firstObject]; CLLocation *location = placemark.location; CLLocationDistance meters = [location distanceFromLocation:self.currentLocation]; salesPerson.distance = @(meters / 1609.344); }]; [sortAndReloadTableOperation addDependency:geocodeOperation]; // note, the final sort is dependent upon this finishing [queue addOperation:geocodeOperation]; // go ahead and queue up the operation } // now we can queue the sort and reload operation, which won't start until the geocode operations are done [[NSOperationQueue mainQueue] addOperation:sortAndReloadTableOperation]; 

Y el GeocodeOperation es una subclass básica de NSOperation concurrente:

 // GeocodeOperation.h #import <Foundation/Foundation.h> typedef void(^GeocodeCompletionHandler)(NSArray *placemarks, NSError *error); @interface GeocodeOperation : NSOperation @property (nonatomic, copy) GeocodeCompletionHandler geocodeCompletionHandler; - (instancetype)initWithPostalCode:(NSString *)postalCode completionHandler:(GeocodeCompletionHandler)geocodeCompletionHandler; @end 

y la implementación (tenga en count que el método main es el único bit interesante aquí … el rest es el código de subclass NSOperation rutina, personalmente, muevo todo el material NSOperation concurrente en una class base, que limpia este código GeocodeOperation , pero no quería confundir esto más, así que he mantenido esto simple):

 // GeocodeOperation.m #import "GeocodeOperation.h" @import CoreLocation; @interface GeocodeOperation () @property (nonatomic, readwrite, getter = isFinished) BOOL finished; @property (nonatomic, readwrite, getter = isExecuting) BOOL executing; @property (nonatomic, copy) NSString *postalCode; @end @implementation GeocodeOperation @synthesize finished = _finished; @synthesize executing = _executing; - (CLGeocoder *)shanetworkingGeocoder { static CLGeocoder *geocoder = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ geocoder = [[CLGeocoder alloc]init]; }); return geocoder; } - (instancetype)initWithPostalCode:(NSString *)postalCode completionHandler:(GeocodeCompletionHandler)geocodeCompletionHandler { self = [super init]; if (self) { _postalCode = [postalCode copy]; _geocodeCompletionHandler = geocodeCompletionHandler; } return self; } - (void)main { [[self shanetworkingGeocoder] geocodeAddressString:self.postalCode completionHandler:^(NSArray *placemarks, NSError *error) { if (self.geocodeCompletionHandler) { self.geocodeCompletionHandler(placemarks, error); } [self completeOperation]; }]; } #pragma mark - NSOperation methods - (void)start { if ([self isCancelled]) { self.finished = YES; return; } self.executing = YES; [self main]; } - (void)completeOperation { self.executing = NO; self.finished = YES; } - (BOOL)isConcurrent { return YES; } - (void)setExecuting:(BOOL)executing { if (_executing != executing) { [self willChangeValueForKey:@"isExecuting"]; _executing = executing; [self didChangeValueForKey:@"isExecuting"]; } } - (void)setFinished:(BOOL)finished { if (_finished != finished) { [self willChangeValueForKey:@"isFinished"]; _finished = finished; [self didChangeValueForKey:@"isFinished"]; } } @end 

Creo que el problema es que el código postal es un NSString . Entonces, en su bloque (p1.postcode > p2.postcode) está comparando las UBICACIONES DE DIRECCIÓN, no los valores de cadena en sí.

Desea utilizar la comparación de la function NSString compare: lugar de hacerlo usted mismo.

Prueba esto:

 [_salesArray sortUsingComparator: ^NSComparisonResult(id obj1, id obj2){ sales *p1 = (sales *)obj1; sales *p2 = (sales *)obj2; NSString *postcode1 = p1.postcode; NSString *postcode2 = p2.postcode; return [postcode1 compare:posecode2]; ];