0

Here I have 2 views:

  • WallViewController
  • TableViewController

WallViewController contains MKMapView, and TableViewController is a subclass of PFQueryTableViewController displaying rows of content that associate with annotation pin on WallViewController. TableViewController is added as subview in WallViewController, so these 2 views are displayed together. Now when I tap annotation pin on the map, the tableview in TableViewController will scroll to the row that associates with that annotation pin selected successfully. But what I want to do is to also have a reversed action of that, so when I tap a row, the annotation pin that associates with that row will be selected and also its callout will be displayed.

Here below I provided relevant codes:

WallViewController.h

@interface WallViewController : UIViewController <MKMapViewDelegate, CLLocationManagerDelegate>

@property (nonatomic, strong) IBOutlet MKMapView *mapView;
@end

@protocol WallViewControllerHighlight <NSObject>
- (void)highlightCellForPost:(PAWPost *)post;
- (void)unhighlightCellForPost:(PAWPost *)post;
@end

WallViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    self.tableViewController = [[TableViewController alloc] initWithStyle:UITableViewStylePlain];
    [self addChildViewController:self.tableViewController];

    self.tableViewController.view.frame = CGRectMake(6.0f, 215.0f, 308.0f, self.view.bounds.size.height - 215.0f);
    [self.view addSubview:self.tableViewController.view];
    self.wallPostsTableViewController.wallViewController = self;
    ....
    }

- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
    id<MKAnnotation> annotation = [view annotation];
    if ([annotation isKindOfClass:[PAWPost class]]) {
        PAWPost *post = [view annotation];                        
        [PostsTableViewController highlightCellForPost:post];
    } else if ([annotation isKindOfClass:[MKUserLocation class]]) {
        // Center the map on the user's current location:
        AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
        MKCoordinateRegion newRegion = MKCoordinateRegionMakeWithDistance(appDelegate.currentLocation.coordinate, appDelegate.filterDistance * 2, appDelegate.filterDistance * 2);

        [self.mapView setRegion:newRegion animated:YES];
        self.mapPannedSinceLocationUpdate = NO;
    }
}

TableViewController.h

@interface TableViewController : PFQueryTableViewController <WallViewControllerHighlight>

@property (nonatomic, strong)  PAWWallViewController *wallViewController;
- (void)highlightCellForPost:(PAWPost *)post;
- (void)unhighlightCellForPost:(PAWPost *)post;
@end

TableViewController.m

....
@synthesize wallViewController;
....

- (void)highlightCellForPost:(PAWPost *)post {
    // Find the cell matching this object.
    for (PFObject *object in [self objects]) {
        PAWPost *postFromObject = [[PAWPost alloc] initWithPFObject:object];
        if ([post equalToPost:postFromObject]) {
            // We found the object, scroll to the cell position where this object is.
            NSUInteger index = [[self objects] indexOfObject:object];

            NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:kTableViewMainSection];
            [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
            [self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];

            return;
        }
    }

    // Don't scroll for posts outside the search radius.
    if ([post.title compare:kWallCantViewPost] != NSOrderedSame) {
        // We couldn't find the post, so scroll down to the load more cell.
        NSUInteger rows = [self.tableView numberOfRowsInSection:kTableViewMainSection];
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:(rows - 1) inSection:kTableViewMainSection];
        [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
        [self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
        [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
    }
}

- (void)unhighlightCellForPost:(PAWPost *)post {
    // Deselect the post's row.
    for (PFObject *object in [self objects]) {
        PAWPost *postFromObject = [[PAWPost alloc] initWithPFObject:object];
        if ([post equalToPost:postFromObject]) {
            NSUInteger index = [[self objects] indexOfObject:object];
            NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
            [self.tableView deselectRowAtIndexPath:indexPath animated:YES];

            return;
        }
    }
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    // call super because we're a custom subclass.
    [super tableView:tableView didSelectRowAtIndexPath:indexPath];

    [tableView deselectRowAtIndexPath:indexPath animated:YES];

    // highlight annotation pin on map - my problem is here ????

    NSLog(@"tap row --> %i", indexPath.row); //******1*****
    NSLog(@"annotation count --> %i", [self.objects count]); //*****2*****
    UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
    UILabel *textLabel = (UILabel*) [selectedCell.contentView viewWithTag:kPAWCellTextLabelTag];
    NSLog(@"textlabel at the table row tapped --> %@", textLabel.text); //*****3*****


    for (NSInteger i = 0; i < [self.objects count]; i++) {

        PFObject *object = [self.objects objectAtIndex:i];
        PAWPost *postFromObject = [[PAWPost alloc] initWithPFObject:object];
        NSString *text = postFromObject.title;
        NSLog(@"text retrieved from PAWPost --> %@", text); //*****4*****

        if ([text isEqualToString:textLabel.text]) {
            NSLog(@"found matching at %i", i); //*****5*****
            [wallViewController.mapView selectAnnotation:[[wallViewController.mapView annotations] objectAtIndex:i] animated:YES];
            NSLog(@"wallViewController --> %@", wallViewController); //*****6*****
            NSLog(@"wallViewController mapView --> %@", wallViewController.mapView); //*****7*****
        }

    }
}

My problem is in the last method (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath in TableViewController.m. For now I have the code as you see, using:

[wallViewController.mapView selectAnnotation:[[wallViewController.mapView annotations] objectAtIndex:i] animated:YES];

But it doesn't seem to work. There must be something wrong here but I don't know what it is.

Here I provided my log output once I tap the first row of the table:

2015-01-28 11:24:31.886 Test[8097:60b] tap row --> 0
2015-01-28 11:24:31.888 Test[8097:60b] annotation count --> 40
2015-01-28 11:24:31.890 Test[8097:60b] textlabel at the table row tapped --> Asddsdtest
2015-01-28 11:24:31.890 Test[8097:60b] text retrieved from PAWPost --> ...(40 texts here)
2015-01-28 11:24:31.897 Test[8097:60b] found matching at 0
2015-01-28 11:24:31.899 Test[8097:60b] wallViewController --> (null)
2015-01-28 11:24:31.900 Test[8097:60b] wallViewController mapView --> (null)

P.S. I found this question but the answer and comments do not seem to provide enough details for beginner like myself.

Additional Info added

PAWPost.h

@interface PAWPost : NSObject <MKAnnotation>

//@protocol MKAnnotation <NSObject>

// Center latitude and longitude of the annotion view.
// The implementation of this property must be KVO compliant.
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;

// @optional
// Title and subtitle for use by selection UI.
@property (nonatomic, readonly, copy) NSString *title;
@property (nonatomic, readonly, copy) NSString *subtitle;
// @end

// Other properties:
@property (nonatomic, readonly, strong) PFObject *object;
@property (nonatomic, readonly, strong) PFGeoPoint *geopoint;
@property (nonatomic, readonly, strong) PFUser *user;
@property (nonatomic, assign) BOOL animatesDrop;
@property (nonatomic, readonly) MKPinAnnotationColor pinColor;

// Designated initializer.
- (id)initWithCoordinate:(CLLocationCoordinate2D)coordinate andTitle:(NSString   *)title andSubtitle:(NSString *)subtitle;
- (id)initWithPFObject:(PFObject *)object;
- (BOOL)equalToPost:(PAWPost *)aPost;

- (void)setTitleAndSubtitleOutsideDistance:(BOOL)outside;

@end

PAWPost.m

@interface PAWPost ()

// Redefine these properties to make them read/write for internal class accesses and mutations.
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;

@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;

@property (nonatomic, strong) PFObject *object;
@property (nonatomic, strong) PFGeoPoint *geopoint;
@property (nonatomic, strong) PFUser *user;
@property (nonatomic, assign) MKPinAnnotationColor pinColor;

@end

@implementation PAWPost

- (id)initWithCoordinate:(CLLocationCoordinate2D)aCoordinate andTitle:(NSString *)aTitle andSubtitle:(NSString *)aSubtitle {
    self = [super init];
    if (self) {
        self.coordinate = aCoordinate;
        self.title = aTitle;
        self.subtitle = aSubtitle;
        self.animatesDrop = NO;
    }
    return self;
}

- (id)initWithPFObject:(PFObject *)anObject {
    self.object = anObject;
    self.geopoint = [anObject objectForKey:kPAWParseLocationKey];
    self.user = [anObject objectForKey:kPAWParseUserKey];

    [anObject fetchIfNeeded]; 
    CLLocationCoordinate2D aCoordinate = CLLocationCoordinate2DMake(self.geopoint.latitude, self.geopoint.longitude);
    NSString *aTitle = [anObject objectForKey:kPAWParseTextKey];
    NSString *aSubtitle = [[anObject objectForKey:kPAWParseUserKey] objectForKey:kPAWParseUsernameKey];

    return [self initWithCoordinate:aCoordinate andTitle:aTitle andSubtitle:aSubtitle];
}

- (BOOL)equalToPost:(PAWPost *)aPost {
    if (aPost == nil) {
        return NO;
   }

    if (aPost.object && self.object) {
        // We have a PFObject inside the PAWPost, use that instead.
        if ([aPost.object.objectId compare:self.object.objectId] != NSOrderedSame) {
            return NO;
        }
        return YES;
    } else {
        // Fallback code:

        if ([aPost.title compare:self.title] != NSOrderedSame ||
        [aPost.subtitle compare:self.subtitle] != NSOrderedSame ||
        aPost.coordinate.latitude != self.coordinate.latitude ||
        aPost.coordinate.longitude != self.coordinate.longitude ) {
            return NO;
        }

        return YES;
    }
}

- (void)setTitleAndSubtitleOutsideDistance:(BOOL)outside {
    if (outside) {
        self.subtitle = nil;
        self.title = kPAWWallCantViewPost;
        self.pinColor = MKPinAnnotationColorRed;
    } else {
        self.title = [self.object objectForKey:kPAWParseTextKey];
        //self.subtitle = [[self.object objectForKey:kPAWParseUserKey] objectForKey:kPAWParseUsernameKey];
        self.subtitle = [NSString stringWithFormat:@"@%@",[[self.object objectForKey:kPAWParseUserKey] objectForKey:kPAWParseUsernameKey]];
        self.pinColor = MKPinAnnotationColorGreen;
    }
}

@end
Community
  • 1
  • 1
SanitLee
  • 1,253
  • 1
  • 12
  • 29
  • 1
    The annotations in WallVC are of type `PAWPost`. The rows in TableVC are of type `PFObject`. In `didSelectRowAtIndexPath`, you need to loop through the map view's `annotations` array and find which one (`PAWPost`) matches the selected `PFObject` (which is `[self.objects objectAtIndex:indexPath.row]`? not sure how your `objects` array corresponds to the table view sections/rows). Once the matching annotation is found, you can call selectAnnotation with it (you may want to first center the map on it to make sure it's visible otherwise selectAnnotation will do nothing). –  Jan 27 '15 at 15:09
  • @Anna Thanks so much for jumping in. I'm trying to do what you suggested and having a hard time putting my logic of my coding right to do so and yet without success. Please see my updated **TableViewController.m** at **(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath**. – SanitLee Jan 28 '15 at 01:23
  • @Anna I feel this line **[wallViewController.mapView selectAnnotation:[[wallViewController.mapView annotations] objectAtIndex:i] animated:YES];** alone doesn't seem to work whatsoever. It supposes to select a pin right? even though not the right one. At least it should give me a good sign that it's working. What am I missing? – SanitLee Jan 28 '15 at 01:34
  • 1
    Break up that long, nested statement into multiple ones so it's easier to debug. Check variable values at each step (with NSLogs). Is wallViewController nil? Is wallViewController.mapView nil? Etc. Step through the code in the debugger. Does the selectAnnotation line actually get executed? Make sure the code is doing what you think it's doing. –  Jan 28 '15 at 03:06
  • I did NSLog and found wallViewController and wallViewController.mapView are null. I updated question and put NSLog output there. Do you know why? – SanitLee Jan 28 '15 at 03:42
  • I added this into my viewDidLoad: wallViewController = [[PAWWallViewController alloc] init]; wallViewController.mapView = [[MKMapView alloc] initWithFrame:self.view.bounds]; But it still doesn't work. I got this error: Terminating app due to uncaught exception 'NSRangeException', reason: ' -[__NSArrayM objectAtIndex:]: index 1 beyond bounds for empty array'. I don't know why. At least it did something. – SanitLee Jan 28 '15 at 13:09
  • 1
    Why would you create a new instance of WallVC? Your question says "TableVC is added as subview in WallVC". So a WallVC instance already exists and the wallViewController property in TableVC simply needs to be set to point to this existing instance. When TableVC is created (by WallVC?), set its wallViewController property. –  Jan 28 '15 at 13:35
  • I'm not sure I understand (sorry too beginner). Could you let me know the code to set wallViewController property and put it to where? I think I did that already so that's why I'm asking your code just to make sure we're talking the same thing. – SanitLee Jan 28 '15 at 13:52
  • I'm scratching my head thinking through your last comment. As I said I did but I hope I'm wrong. So I updated my question to show what exactly I've got in viewDidLoad of those 2 views in terms of creating instance, adding subview. Besides you could see in TableViewController.m that I created property of WallViewVC. Please bear with me. – SanitLee Jan 30 '15 at 06:13
  • 1
    I think you are not clear on some basics like class vs. instance and the difference between variable declaration vs. setting a variable value. WallVC seems to be the parent object so TableVC should not create a new instance of WallVC. Instead, in WallVC viewDidLoad, after creating TableVC, do `self.tableViewController.wallViewController = self;`. In TableVC, just do the property declaration -- do not create a new WallVC. –  Jan 30 '15 at 12:41
  • Thank you @Anna and I'll take this comment on my chin. This helps me out of basic stuff mess, my bad. – SanitLee Jan 30 '15 at 15:41

2 Answers2

2

You can use PFObject.objectId to find annotation what you want.

PFObject *selectedObject = [self.objects objectAtIndex:i];

for (id annotation in wallViewController.mapView.annotations)
{
    if ([annotation isKindOfClass:[PAWPost class]])
    {
        PAWPost *post = (PAWPost*)annotation;
        if ([post.object.objectId isEqualToString:selectedObject.objectId])
        {
            [wallViewController.mapView selectAnnotation:annotation  animated:YES];
            break;
        }
    }
}
PowHu
  • 2,159
  • 16
  • 17
  • Thank you so much but it's not working. I found that **For loop** never gets run. I really doubt my wallViewController.map not being set correctly but I don't know what's wrong. I have set property of wallViewController in TableViewController.m also with synthesising it. What else am I missing? – SanitLee Jan 29 '15 at 05:11
  • It turned out to be some basic stuff mess causing this not to work. It's sorted out and your code is working like a champ. Using objectId to find annotation is perfect for me. Thanks very much. – SanitLee Jan 30 '15 at 15:48
1

You can do your annotation as object that support MSAnnotation protocol. So when you select your cell - you'll exactly know what object is behind this action. And in that case you - your annotation will be exactly the object you've selected by tapping on UITableViewCell.

So - your object PAWPost just should support MKAnnotation protocol:

@interface PAWPost : NSObject <MKAnnotation> 
// Interface here
@end

@implementation PAWPost
// ...
- (CLLocationCoordinate2D)coordinate {
    return <#your coordinate here#>;
}
// ...
@end

P.S. You dont need to create new local instance of WallViewController class in didSelectRowAtIndexPath: method. Because at that time your WallViewController will not have MapView. Instead that - create instance of WallViewController before and use pointer to it.

Aleksei Minaev
  • 1,488
  • 16
  • 17
  • Thanks a lot @WinDMak3r . If you don't mind, could you please put your answer in code as well so that I can understand better? Sorry for requesting but I am very beginner preferably trying to learn from examples? Please bear with me. – SanitLee Jan 26 '15 at 04:59
  • I still don't understand. What should I do with your code? – SanitLee Jan 26 '15 at 14:18
  • Use it with your PAWPost class to support annotation. When you select cell in tableView you will get this object from array by using indexPath.row and then use it in selectAnnotation: method. – Aleksei Minaev Jan 26 '15 at 18:30
  • Embarrassed to say that I still don't get it but I will never give up. Definitely I have to get myself understood well enough about protocol stuff. Btw, I added details of PAWPost class that I've got now. Originally I did something like this **[wallViewController.mapView selectAnnotation:[[wallViewController.mapView annotations] objectAtIndex:indexPath.row] animated:YES];**. I know it's not going to work though. I just don't know how to link annotation with indexPath.row. It'd be great help if you edit my code to achieve that. – SanitLee Jan 27 '15 at 07:34
  • In your tableViewController you need to add another property and connect it with existing MapViewController. After that you could easily do something like that: `[wallViewController.mapView selectAnnotation:self.objects[indexPath.row] animated:YES]` – Aleksei Minaev Jan 27 '15 at 07:43
  • Are you talking about MapViewController property? If yes, I have it. In this case it's wallViewController. – SanitLee Jan 27 '15 at 07:53
  • So use it as I suggested in my previous comment. If you set your wallViewControllerInstance right - all should work :) – Aleksei Minaev Jan 27 '15 at 10:51
  • I have updated **TableViewController.m**. Could you please help check it out and let me know if anything could be wrong? – SanitLee Jan 27 '15 at 13:40
  • I sat back and read through your answer again. You mentioned about creating instance of WallViewController so I added this into my viewDidLoad: **wallViewController = [[PAWWallViewController alloc] init]; wallViewController.mapView = [[MKMapView alloc] initWithFrame:self.view.bounds];** But it still doesn't work. I got this error: **Terminating app due to uncaught exception 'NSRangeException', reason: ' -[__NSArrayM objectAtIndex:]: index 1 beyond bounds for empty array'**. I don't know why. – SanitLee Jan 28 '15 at 13:07
  • I guess you have iPad xib or storyboard. If so - you shouldn't create PAWWallViewController by yourself. Just create IBOutlet property for it in TableViewController and drag connection from visual interface. – Aleksei Minaev Jan 28 '15 at 17:08
  • No. I'm just doing iPhone xib. – SanitLee Jan 29 '15 at 03:00