Question

Have a problem with my MutableArray. I've the data array in my tableview, and everything is ok, but when the data is sorting by a distance and i'm choosing an item in cell, it shows me incorrect data in new viewcontroller. It shows the data without sorting. It's like a broken link) Hope for your help)

i'm new in obj-c, so i apologise)

here is my code:

       list = [[NSMutableArray alloc] init];

        [list addObject:@{@"name": @"Central office", @"address":@"наб. Обводного канала, д.66А", @"phone":@"+7 (812) 320-56-21 (многоканальный)", @"workTime":@"ПН-ПТ: с 9:30 до 18:30", @"email":@"mail@ibins.ru", @"payment":@"Принимаются к оплате пластиковые карты VISA и MasterCard", @"longitude":@30.336842, @"latitude":@59.913950}];

        [list addObject:@{@"name": @"Second office", @"address":@"ул. Камышовая, д.38", @"phone":@"+7 (812) 992-992-6; +7 (812) 456-26-43", @"workTime":@"Ежедневно: с 9:30 до 20:00", @"email":@"sever@ibins.ru", @"payment":@"Принимаются к оплате пластиковые карты VISA и MasterCard", @"longitude":@30.219863, @"latitude":@60.008805}];

        [list addObject:@{@"name": @"Third office", @"address":@"Street name", @"phone":@"phone number", @"workTime":@"Work time", @"email":@"email address", @"longitude":@30.294254, @"latitude":@60.028728}]; 

    [self constructList];
    [self constructPins];
    }



- (IBAction)switchDisplayType:(id)sender {
    [UIView beginAnimations:@"View Flip" context:nil];
    [UIView setAnimationDuration:0.80];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
    [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO];
    [UIView commitAnimations];

    if ([(UISegmentedControl*)sender selectedSegmentIndex] == 1) {
        map.hidden = YES;
        contentSV.hidden = NO;
    }
    else {
        map.hidden = NO;
        contentSV.hidden = YES;
    }
}

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
    static NSString* BridgeAnnotationIdentifier = @"bridgeAnnotationIdentifier";

    MKPinAnnotationView* customPinView = [[[MKPinAnnotationView alloc]
                                           initWithAnnotation:annotation reuseIdentifier:BridgeAnnotationIdentifier] autorelease];
    customPinView.pinColor = MKPinAnnotationColorRed;
    customPinView.animatesDrop = YES;
    customPinView.canShowCallout = YES;

    UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
   // NSLog(@"%@",rightButton);
    [rightButton addTarget:self
                    action:@selector(annotationButtonTapped:)
          forControlEvents:UIControlEventTouchUpInside];
    customPinView.rightCalloutAccessoryView = rightButton;
    return customPinView;//[kml viewForAnnotation:annotation];
}

- (void)annotationButtonTapped:(id)sender {
    DetailVC *sampleVC = [[DetailVC alloc] initWithNibName:@"DetailVC" bundle:[NSBundle mainBundle]];
    [self.navigationController pushViewController:sampleVC animated:YES];
    [sampleVC release];

    for (NSDictionary *dict in list) {
        if ([[dict valueForKey:@"name"] isEqualToString:selectedAnnTitle]) {
            [sampleVC updateViewWithDict:dict];
        }
    }
}



- (void)constructPins {
    for (NSDictionary *dict in list) {
        MKCoordinateRegion region;
        MKCoordinateSpan span;
        span.latitudeDelta = 0.3;
        span.longitudeDelta = 0.3;

        CLLocationCoordinate2D location;

        location.latitude = [[dict valueForKey:@"latitude"] floatValue];
        location.longitude = [[dict valueForKey:@"longitude"] floatValue];

        if (location.latitude == 0.0 && location.longitude == 0.0)
            return;

        region.span = span;
        region.center = location;

        MKPointAnnotation *point = [[MKPointAnnotation alloc] init];
        point.coordinate = location;
        point.title = [dict valueForKey:@"name"];

        [map addAnnotation:point];
        [map setRegion:region animated:YES];
    }
}

- (void)constructList {
    int n = 0;
    for (NSDictionary *dict in list) {
        UIView *container = [[UIView alloc] initWithFrame:CGRectMake(0, 100*n, 320, 100)];
        container.backgroundColor = [UIColor whiteColor];

        UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 320, 100)];
        button.tag = n;
        [button addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
        [container addSubview:button];


        NSString *str = [NSString stringWithFormat:@"%@\nAddress: %@\nPhone: %@\nWork Time: %@", [dict valueForKey:@"name"], [dict valueForKey:@"address"], [dict valueForKey:@"phone"], [dict valueForKey:@"workTime"]];
        UILabel *nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 300, 80)];
        nameLabel.textAlignment = NSTextAlignmentLeft;
        nameLabel.numberOfLines = 0;
        nameLabel.text = str;
        nameLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:12.0];
        [container addSubview:nameLabel];


        UIView *separator = [[UIView alloc]initWithFrame:CGRectMake(0, 99, 320, 1)];
        separator.backgroundColor = [UIColor darkGrayColor];
        [container addSubview:separator];


        [contentSV addSubview:container];


        n++;
        contentSV.contentSize = CGSizeMake(0, 100*n);
    }
}

- (float)distanceBetweenLat1:(float)tlat1 lon1:(float)tlon1 lat2:(float)tlat2 lon2:(float)tlon2 {
    float result = 0.0;
    int R = 6371;

    float currentLatitude = tlat1;
    float currentLongtitude = tlon1;

    float lat2 = tlat2;
    float lon2 = tlon2;

    float dLat = (lat2-currentLatitude)*M_PI/180;
    float dLon = (lon2-currentLongtitude)*M_PI/180;
    float nlLat = currentLatitude*M_PI/180;
    lat2 = lat2*M_PI/180;

    float a = sin(dLat/2) * sin(dLat/2) +  sin(dLon/2) * sin(dLon/2) * cos(nlLat) * cos(lat2);
    float c = 2 * atan2(sqrt(a), sqrt(1-a));
    result = R * c;

    return result;
}

- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
    selectedAnnTitle = [view.annotation title];

    return;

    DetailVC *sampleVC = [[DetailVC alloc] initWithNibName:@"DetailVC" bundle:[NSBundle mainBundle]];
    [self.navigationController pushViewController:sampleVC animated:YES];


    NSString *ttl = [view.annotation title];

    for (NSDictionary *dict in list) {
        if ([[dict valueForKey:@"name"] isEqualToString:ttl]) {
            [sampleVC updateViewWithDict:dict];
        }
    }

    [map deselectAnnotation:view.annotation animated:NO];
}

- (void)buttonTapped:(id)sender {
    DetailVC *sampleVC = [[DetailVC alloc] initWithNibName:@"DetailVC" bundle:[NSBundle mainBundle]];
    [self.navigationController pushViewController:sampleVC animated:YES];


    int n = 0;
    for (NSDictionary *dict in list) {
        if (n == [sender tag]) {
            [sampleVC updateViewWithDict:dict];
        }
        n++;
    }
}

- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation {
    NSLog(@"didUpdateUserLocation");

    MKAnnotationView* annotationView = [mapView viewForAnnotation:userLocation];
    annotationView.canShowCallout = NO;

    if (!userLocationUpdated) {
        if (userLocation.coordinate.latitude > 0.1 && userLocation.coordinate.longitude > 0.1) {
         //   NSLog(@"SORT BY DISTANCE");

            userLocationUpdated = YES;

            for (int i = 0; i < [list count]; i++) {
                NSMutableDictionary *record = [[NSMutableDictionary alloc] initWithDictionary:[list objectAtIndex:i]];

                float latitude = [[record valueForKey:@"latitude"] floatValue];
                float longitude = [[record valueForKey:@"longitude"] floatValue];

                float dist = [self distanceBetweenLat1:map.userLocation.coordinate.latitude lon1:map.userLocation.coordinate.longitude lat2:latitude lon2:longitude];
                NSNumber *distN = [NSNumber numberWithFloat:dist];
                [record setObject:distN forKey:@"distance"];

              [list replaceObjectAtIndex:i withObject:record];
            }

            NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:@"distance" ascending:YES];
            NSArray *itemsListSorted = [[NSArray alloc] initWithArray:list];
            itemsListSorted = [itemsListSorted sortedArrayUsingDescriptors:[NSArray arrayWithObjects:descriptor,nil]];

            for (UIView *view in contentSV.subviews) {
                [view removeFromSuperview];
            }

            for (int i = 0; i < [itemsListSorted count]; i++) {
                UIView *container = [[UIView alloc] initWithFrame:CGRectMake(0, 100*i, 320, 100)];
                container.backgroundColor = [UIColor whiteColor];

                UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 320, 100)];
                button.tag = i;
                [button addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
                [container addSubview:button];


                NSString *str = [NSString stringWithFormat:@"%@\nAddress: %@\nPhone: %@\nWorkTime: %@", [[itemsListSorted objectAtIndex:i] valueForKey:@"name"], [[itemsListSorted objectAtIndex:i] valueForKey:@"address"], [[itemsListSorted objectAtIndex:i] valueForKey:@"phone"], [[itemsListSorted objectAtIndex:i] valueForKey:@"workTime"]];
                UILabel *nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 300, 80)];
                nameLabel.textAlignment = NSTextAlignmentLeft;
                nameLabel.numberOfLines = 0;
                nameLabel.text = str;
                nameLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:12.0];
                [container addSubview:nameLabel];


                UILabel *distanceLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 2, 300, 21)];
                distanceLabel.textAlignment = NSTextAlignmentRight;
                distanceLabel.text = [NSString stringWithFormat:@"%.1f км", [[[itemsListSorted objectAtIndex:i] valueForKey:@"distance"] floatValue]];
                distanceLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:12.0];
                distanceLabel.textColor = [UIColor lightGrayColor];
                [container addSubview:distanceLabel];


                UIView *separator = [[UIView alloc]initWithFrame:CGRectMake(0, 99, 320, 1)];
                separator.backgroundColor = [UIColor darkGrayColor];
                [container addSubview:separator];


                [contentSV addSubview:container];


                contentSV.contentSize = CGSizeMake(0, 100*(i+1));
            }
        }
    }
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

@end
Was it helpful?

Solution

Although there are numerous issues with this code (the least of which are some minor precision issues with the distance calculation), the problem is not with the distance calculation.

The detail view controller shows incorrect data because it is given the data from the list array which is not itself sorted by distance.

In the didUpdateUserLocation delegate method, the contents of list are copied to a local array named itemsListSorted.

Only the itemsListSorted array is sorted by distance and then the display is updated using this local array.

But the original list array (which the buttonTapped method uses as the source of the data to send to the detail view controller) is never updated.

So if "Central Office" is at index 0 in list but gets moved to index 2 in itemsListSorted, the display shows the correct position but when you tap on it, the buttonTapped method sends the item at index 2 from the list array which does not have "Central Office" (it's still at index 0 in list).


One way to fix this problem is to stop using a local array and sort the list array directly.

In didUpdateUserLocation, you could replace these two lines:

NSArray *itemsListSorted = [[NSArray alloc] initWithArray:list];
itemsListSorted = [itemsListSorted sortedArrayUsingDescriptors:[NSArray arrayWithObjects:descriptor,nil]];

with this:

[list sortUsingDescriptors:[NSArray arrayWithObjects:descriptor,nil]];

and then replace all references to itemsListSorted in the same method with list.



Regarding the other issues with the code, there is not enough room to point them all out or explain them in one answer. However, here are just a few highlights:

  • Instead of a manually creating a "table view", use an actual UITableView.
  • Instead of manually calculating distance between coordinates, use the distanceFromLocation method in the CLLocation class or the MKMetersBetweenMapPoints function.
  • In viewForAnnotation, you should return nil if annotation is of type MKUserLocation otherwise it will appear as a red pin just like the others.
  • Use ARC instead of manual memory management.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top