1

I have this XML which I display in UITableView:

<result>
<trip duration="03:30">
<takeoff date="2010-06-19" time="18:40" city="Moscow"/>
<landing date="2010-06-19" time="20:10" city="Novgorod"/>
<flight carrier="Rossiya" number="8395" eq="320"/>
<price>13429.00</price>
</trip>
<trip duration="03:40">
<takeoff date="2010-06-19" time="09:20" city="Omsk"/>
<landing date="2010-06-19" time="11:15" city="Paris"/>
<flight carrier="AirFrance" number="1145" eq="320"/>
<price>13229.00</price>
</trip>
<trip duration="03:50">
<takeoff date="2010-06-19" time="07:20" city="Omsk"/>
<landing date="2010-06-19" time="14:15" city="Barcelona"/>
<flight carrier="AirFrance" number="1100" eq="320"/>
<price>13329.00</price>
</trip>
</result>

I'm using this code to save it:

if ([elementname isEqualToString:@"trip"]) {
    currentTweet = [Tweet alloc];
    isStatus = YES;
    flightTime = [attributeDict objectForKey:@"duration"];
    currentTweet.flightDuration = [NSString stringWithFormat:@"Flight duration is: %@", flightTime];
}

if([elementname isEqualToString:@"takeoff"]) {

    takeoffPlace = [attributeDict objectForKey:@"city"];
} 
if ([elementname isEqualToString:@"landing"]) {
    landingPlace = [attributeDict objectForKey:@"city"];
    currentTweet.content = [NSString stringWithFormat:@"%@ - %@", takeoffPlace, landingPlace];
}

if ([elementname isEqualToString:@"takeoff"]) {
    takeoffDate = [attributeDict objectForKey:@"date"];
    takeoffTime = [attributeDict objectForKey:@"time"];
    currentTweet.takeOffAll = [NSString stringWithFormat:@"Take off date %@. Take off time %@", takeoffDate, takeoffTime];
}

if ([elementname isEqualToString:@"landing"]) {
    landingDate = [attributeDict objectForKey:@"date"];
    landingTime = [attributeDict objectForKey:@"time"];
    currentTweet.landingAll = [NSString stringWithFormat:@"Landing date %@. Landing time %@", landingDate, landingTime];
}

if ([elementname isEqualToString:@"flight"]) {
    companyName = [attributeDict objectForKey:@"carrier"];
    companyNumber = [attributeDict objectForKey:@"number"];
    eqStuff = [attributeDict objectForKey:@"eq"];
    currentTweet.companyInfo = [NSString stringWithFormat:@"Carrier: %@ Flight number: %@ Eq: %@", companyName, companyNumber, eqStuff];
}

I need to sort my TableView by 2 parameters:

  1. The <trip duration="03:50"> time

  2. The <price>

I'm not sure whether I'm using the right structure - basically all the data is NSStrings assigned to a separate data class which are then assigned to UILabels inside UITableView cell.

What would be the best way to sort the data by time and price? Any ideas on how I should reformat the code?

P.S. I'm using SegmentedControl to reload the TableView and sort it accordingly.

Sergey Grischyov
  • 11,995
  • 20
  • 81
  • 120

2 Answers2

2

You need to put that all into an array and then load the UITableView using the array after sorting it.

Tony
  • 4,609
  • 2
  • 22
  • 32
  • Yeah, I thought about that but how do you put so many parameters in a single array? I mean the UITableViewCell holds around 5 strings, how would you distinguish between them? – Sergey Grischyov Nov 27 '12 at 22:41
  • Put the 5 strings into an object that is then put into the array. Then sort those objects using a custom comparator. – Tony Nov 27 '12 at 22:42
  • Sounds great. Any ideas how do you compare time durations like '03:20' with '03:25'? – Sergey Grischyov Nov 27 '12 at 22:43
  • http://stackoverflow.com/questions/805547/how-to-sort-an-nsmutablearray-with-custom-objects-in-it – Tony Nov 27 '12 at 22:46
1

You can store your results in an array of dictionary entries, which you can then sort the array using sortedArrayUsingComparator like I demonstrate at the end of this answer.

For example, my parser was as follows:

@interface XmlParserViewController () <NSXMLParserDelegate>
{
    NSMutableArray *trips;
    NSMutableDictionary *currentTrip;
    NSMutableString *currentElement;
}
@end

@implementation XmlParserViewController


- (void)viewDidLoad
{
    [super viewDidLoad];

    trips = [[NSMutableArray alloc] init];

    // clearly I'm reading your XML from a file in my bundle, but you'll get it
    // how ever you're retrieving it from your remote source

    NSString *filename = [[NSBundle mainBundle] pathForResource:@"results" ofType:@"xml"];
    NSData *data = [NSData dataWithContentsOfFile:filename];
    NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
    parser.delegate = self;
    [parser parse];
}

#pragma mark - NSXMLParserDelegate methods

#define kRowElementTag @"trip"

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict
{
    NSArray *attributeElementNames = @[@"takeoff", @"landing", @"flight"];
    NSArray *foundCharacterElementNames = @[@"price"];

    if ([elementName isEqualToString:kRowElementTag])
    {
        currentTrip = [[NSMutableDictionary alloc] init];
        [trips addObject:currentTrip];
        if (attributeDict)
            [currentTrip setObject:attributeDict forKey:elementName];
    }
    else if (currentTrip)
    {
        if ([attributeElementNames containsObject:elementName])
        {
            if (attributeDict)
                [currentTrip setObject:attributeDict forKey:elementName];
        }
        else if ([foundCharacterElementNames containsObject:elementName] && currentElement == nil)
        {
            // you can change this to just grab a few fields ... add whatever fields you want to this

            currentElement = [[NSMutableString alloc] init];
            [currentTrip setObject:currentElement forKey:elementName];
        }
    }
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
    if ([elementName isEqualToString:kRowElementTag])
    {
        currentTrip = nil;
    }
    else if (currentElement)
    {
        currentElement = nil;
    }
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
    if (currentElement)
    {
        [currentElement appendString:string];
    }
}

- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
{
    NSLog(@"%s error=%@", __FUNCTION__, parseError);

    // we should handle the error here
}

- (void)parserDidEndDocument:(NSXMLParser *)parser
{
    NSLog(@"Order in XML:");

    for (NSDictionary *trip in trips)
    {
        NSLog(@"    %@ %@ %@ %@ %@ %@", trip[@"flight"][@"carrier"], trip[@"flight"][@"eq"], trip[@"takeoff"][@"date"], trip[@"takeoff"][@"time"], trip[@"trip"][@"duration"], trip[@"price"]);
    }

    NSArray *sortedTrips = [self sortResultsByDurationAndPrice];

    NSLog(@"Sorted order:");

    for (NSDictionary *trip in sortedTrips)
    {
        NSLog(@"    %@ %@ %@ %@ %@ %@", trip[@"flight"][@"carrier"], trip[@"flight"][@"eq"], trip[@"takeoff"][@"date"], trip[@"takeoff"][@"time"], trip[@"trip"][@"duration"], trip[@"price"]);
    }

    // you can now update your user interface or do whatever at this point
    //
    // [self.tableView reloadData];
}

You'll notice that that parserDidEndDocument calls sortResultsByDurationAndPrice which looks like:

- (NSArray *)sortResultsByDurationAndPrice
{
    NSArray *sortedTrips = [trips sortedArrayUsingComparator: ^(id trip1, id trip2) {

        double duration1 = [self durationFromString:trip1[@"trip"][@"duration"]];
        double duration2 = [self durationFromString:trip2[@"trip"][@"duration"]];

        double price1 = [trip1[@"price"] doubleValue];
        double price2 = [trip2[@"price"] doubleValue];

        if (duration1 < duration2 || (duration1 == duration2 && price1 < price2)) {
            return (NSComparisonResult)NSOrderedAscending;
        }

        if (duration1 > duration2 || (duration1 == duration2 && price1 > price2)) {
            return (NSComparisonResult)NSOrderedDescending;
        }

        return (NSComparisonResult)NSOrderedSame;
    }];

    return sortedTrips;
}

// this assumes that duration is in the form of "h:mm"

- (double)durationFromString:(NSString *)durationString
{
    NSArray *durationArray = [durationString componentsSeparatedByString:@":"];

    return [durationArray[0] doubleValue] + [durationArray[1] doubleValue] / 60.0;
}

// sort by date and time not used, but was part of my original answer
// so I provide it here for the sake of completeness

- (NSArray *)sortResultsByDateTime
{
    NSArray *sortedTrips = [trips sortedArrayUsingComparator: ^(id trip1, id trip2) {

        NSComparisonResult dateComparison = [trip1[@"takeoff"][@"date"] compare:trip2[@"takeoff"][@"date"]];
        NSComparisonResult timeComparison = [trip1[@"takeoff"][@"time"] compare:trip2[@"takeoff"][@"time"]];

        if (dateComparison == NSOrderedAscending || (dateComparison == NSOrderedSame && timeComparison == NSOrderedAscending)) {
            return (NSComparisonResult)NSOrderedAscending;
        }

        if (dateComparison == NSOrderedDescending || (dateComparison == NSOrderedSame && timeComparison == NSOrderedDescending)) {
            return (NSComparisonResult)NSOrderedDescending;
        }

        return (NSComparisonResult)NSOrderedSame;
    }];

    return sortedTrips;
}

Clearly, you can just tweak your sortResults based upon your personal array of dictionary entries, but this should give you an idea of how it could work.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thanks for the verbose explanation. I'll try it out and report back. – Sergey Grischyov Nov 27 '12 at 23:07
  • @SergiusGee I realized that I was sorting by date and time, whereas you wanted it sorted by duration and price, so I've modified my answer accordingly. – Rob Nov 28 '12 at 00:16