按距离对UITableView进行排序 [英] Sort UITableView by distance

查看:98
本文介绍了按距离对UITableView进行排序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试根据从坐标计算出的距离以升序对表格视图进行排序.一切都像一种魅力,只是我无法按升序获得它,我一直在乱搞NSSortDescriptor等,但是不幸的是,任何帮助将不胜感激,这是我的代码:

I am trying to sort my tableview in ascending order by distance that I calculate from coordinates. Everything works like a charm except I can't get it in ascending order, I have been mucking around with NSSortDescriptor etc., but getting unlucky, any help would be appreciated, here is my code:

- (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)NSOrderedDescending;
             }

             if (p1.postcode < p2.postcode) {
                 return (NSComparisonResult)NSOrderedAscending;
             }
             return (NSComparisonResult)NSOrderedSame;
         }
     ];

     [self.tableView reloadData];
}

推荐答案

这里有一些问题:

  1. geocodeAddressString施加了一些限制,如文档中所述:

  1. The geocodeAddressString imposes a few limitations, as outlined in the documentation:

此方法将指定的位置数据异步提交到地理编码服务器并返回.您的完成处理程序块将在主线程上执行.发起前向地理编码请求后,请勿尝试发起另一个前向或反向地理编码请求.

This method submits the specified location data to the geocoding server asynchronously and returns. Your completion handler block will be executed on the main thread. After initiating a forward-geocoding request, do not attempt to initiate another forward- or reverse-geocoding request.

地理编码请求受每个应用程序的速率限制,因此在短时间内提出过多请求可能会导致某些请求失败.当超出最大速率时,地理编码器会将值为kCLErrorNetwork的错误对象传递给完成处理程序.

Geocoding requests are rate-limited for each app, so making too many requests in a short period of time may cause some of the requests to fail. When the maximum rate is exceeded, the geocoder passes an error object with the value kCLErrorNetwork to your completion handler.

这里有几个主要观察结果:

Several key observations here:

  • 这是异步运行的(因此您不能调用geocodeAddressString并随后立即使用其结果).您确实在地理编码内部完成区块中调用工作.

  • This runs asynchronously (so you cannot call geocodeAddressString and use its results immediately afterwards). You have do invoke the work contingent on the geocoding inside the completion block.

在上一个地理编码请求完成之前,您不应该开始下一个地理编码请求.

You should not be starting the next geocode request until the prior one completes.

这意味着您必须对第一个邮政编码进行地理编码,使其异步完成(即稍后),对下一个邮政编码进行地理编码,使其完成等,然后进行排序并重新加载表格.一个简单的for循环不是执行此操作的适当方法.您可以编写执行单个地理编码并在完成代码块中调用下一个地理编码的方法,也可以使用下面的NSOperation子类.

This means that you have to geocode the first postal code, let it complete asynchronously (i.e. later), geocode the next one, let it complete, etc., and only then do your sort and reload the table. A simple for loop is not an appropriate way to do this. You can either write a method that does a single geocode and invokes the next geocode in the completion block, or you can use NSOperation subclass as I have below.

我建议将distance存储为NSNumber.在MVC中,小数点后一位字符串表示是一种视图"行为,并且可能不应该是模型"的一部分.

I would advise storing the distance as a NSNumber. In MVC, the one decimal place string representation is a "view" behavior, and should probably not be part of the "model".

这样做的好处是,当您要对对象进行排序时,可以简单地为NSNumber调用compare方法.例如,如果salesPersonnel是对象的NSMutableArray,而每个SalesPerson对象具有名为distanceNSNumber属性,则可以执行以下操作:

The advantage of this is that when you want to sort the objects, you can simply invoke the compare method for the NSNumber. For example, if salesPersonnel was a NSMutableArray of objects which each SalesPerson object has the NSNumber property called distance, you could then do:

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

我不确定您的sales条目是否属于实际的销售交易或销售人员,因此,如果我误解了对象类型,我深表歉意,但希望这可以说明这个想法.

I wasn't sure if your sales entries per actual sales transactions or sales personnel, so I apologize if I misinterpreted the object types, but hopefully this illustrates the idea.


您可以按照自己想要的任何方式执行此操作,但是对我来说,当我想运行多个异步任务但要依次执行时,我倾向于并发NSOperation子类,该子类将添加到串行.


You can do this any way you want, but for me, when I want to run a number of asynchronous tasks, but do so sequentially, I gravitate to concurrent NSOperation subclass which I'll add to a serial NSOperationQueue.

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];

GeocodeOperation是基本的并发NSOperation子类:

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

和实现(请注意,main方法是这里唯一有趣的部分...其余都是常规的并发NSOperation子类代码;就我个人而言,我将所有并发的NSOperation内容移至一个基类中类,它清除了此GeocodeOperation代码,但我不想进一步混淆它,因此我将其保持简单):

and the implementation (note, the main method is the only interesting bit here ... all the rest is routine concurrent NSOperation subclass code; personally, I move all of the concurrent NSOperation stuff into a base class, which cleans up this GeocodeOperation code, but I didn't want to confuse this further, so I've kept this 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 *)sharedGeocoder
{
    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 sharedGeocoder] 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

这篇关于按距离对UITableView进行排序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆