Unable to pass annotation data to tableviewcontroller - ios

EDITED QUESTION:
I'm trying to take annotations created in map view (with title/subtitle) and push all annotations on my map to a tableview showing the list with title/subtitle.
I have a RegionAnnotation.h/.m NSObject file that works the reverse geocoding I need to populate the pins on my MapViewController. This works just fine. I do a long press which create a pin and the title and subtitle show up and the reverse geocoding works.
Now I want to push the pin data to a tableview list. I have tried calling the region annotation information within the cellForRowAtIndexPath and I use UITableViewCellStyleSubtitle in order to get the correct format for the cell to populate the title and subtitle. However when I call the following:
if (cell == nil){
NSLog(#"if cell");
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
CLPlacemark *pins = [self.annotations objectAtIndex:indexPath.row];
RegionAnnotation *annotation = [[RegionAnnotation alloc] initWithLocationIdentifier:(NSString *)pins];
cell.textLabel.text = annotation.title;
I only get the title, which is this case is "Location Reminder", however, the subtitle information which is the address does not populate the table. All that shows is the text "subtitle".
How do I populate the cell with both title and subtile information. I've been working on this for over a month and can't seem to find a solution. Please help.

Some comments on the latest code posted:
CLPlacemark *pins = [self.annotations objectAtIndex:indexPath.row];
RegionAnnotation *annotation = [[RegionAnnotation alloc]
initWithLocationIdentifier:(NSString *)pins];
This doesn't look right. The pins variable is declared as a CLPlacemark (which is suspicious in itself because CLPlacemark doesn't conform to MKAnnotation so why is it in an "annotations" array?) and then it is being casted an NSString * which has no relationship to a CLPlacemark. The cast is not going convert pins into a string -- it's going to treat the data pointed to by pins as if it was a NSString (which it isn't).
There are too many other issues, questions and unknowns with the previous code that was posted as well.
Instead, I'll give an example of how you might pass the annotations that are on the map view to a table view and show the data in the cells...
In the PinListViewController (the one with the table view), we declare an NSArray property to receive and reference the annotations:
//in the .h:
//Don't bother declaring an ivar with the same name.
#property (nonatomic, retain) NSArray *annotations;
//in the .m:
#synthesize annotations;
Next, in the MapViewController, in the place where you want to present/push/show the PinListViewController, the code would be something like this:
PinListViewController *plvc = [[PinListViewController alloc] init...
//pass the map view's annotations array...
plvc.annotations = mapView.annotations;
[self presentModalViewController:plvc animated:YES]; //or push, etc.
[plvc release]; //remove if using ARC
An important point here is that this example sends the entire annotations array. If you are showing the user's current location using showsUserLocation = YES then the array will include that annotation as well. If you only want to send certain annotations, you'll have to first build a new array containing the ones you want from the map view array and set plvc.annotations equal to that new array. A simple way to do that is to loop through the mapView.annotations and if an annotation is one you want to include, add it to the new array using addObject. Another possible issue with using the map view's annotations array directly is that if the annotations on the map change (are added/removed) while the table view is still showing the annotations list, it will go out of sync and may cause run-time range exceptions. To avoid that, if necessary, you could set plvc.annotations to a copy of the map view's annotations array (ie. [mapView.annotations copy]).
In PinListViewController, in the numberOfRowsInSection method:
return self.annotations.count;
In PinListViewController, in the cellForRowAtIndexPath method:
//typical dequeue/alloc+init stuff here...
//assume cell style is set to UITableViewCellStyleSubtitle
id<MKAnnotation> annotation = [self.annotations objectAtIndex:indexPath.row];
cell.textLabel.text = annotation.title;
cell.detailTextLabel.text = annotation.subtitle;
return cell;
The annotation is declared as id<MKAnnotation> so it will work with any type of annotation since the example only needs to show the standard title and subtitle properties. If you needed to show custom properties you may have in your custom annotation class, you would use isKindOfClass to check if annotation is of that type and then you can cast it to that custom class and reference the custom properties.

Related

DidSelectedRow for Setting Title

So, I have one ViewController that has a TableView populate with an Array (_vinhoArray) and another TableViewController that opens when a user taps a row in this first ViewController.
What I want is set the title of second View ( TableViewController) for the name of row selected.
In this first one I have this code.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *selectedRow = [_vinhoArray objectAtIndex:indexPath.row];
// SubTable is the name of Second View
SubTable *subTable = [[SubTable alloc] init];
subTable.titleView = selectedRow;
}
In the second view a have a property set
#property (nonatomic, strong) NSString *titleView;
In the -(void)viewDidLoad I have this code
self.title = titleView;
But nothing shows in the title of the Second View (TableViewController)
Thats it! Please Help
If you simply want to set the title on the navigation bar of the view controller you are presenting, there's no need to create a property for it. You probably want to do something like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *titleString = [_vinhoArray objectAtIndex:indexPath.row];
// SubTable is the name of Second View
SubTable *subTable = [[SubTable alloc] init];
subTable.title = titleString;
[self.navigationController pushViewController:subTable animated:YES];
}
Above seems to be the easiest and most straight-forward way to implement this. On a side note, it's pretty unconventional to name a variable type like an NSString with some other class type, like titleView or titleLabel. Something similar to "titleString", like I used, would be more appropriate and less confusing for anyone reading your code.
What is probably happening is that the view is loading before you set the titleView property. As you are setting the title statically in viewDidLoad, no changes are being shown. You can confirm this by putting in a couple of breakpoints and seeing the order of the calls being made.
One solution to this is to use a more dynamic method of setting the table. Create a custom accessor in your second view controller:
- (void)setTitleView:(NSString *)title {
_titleView = title; // This sets the backing iVar
self.title = titleView;
}
Now, whenever you set the property, then the change is applied, even if it is after the view has loaded.
You don't need to declare the method or the iVar, these are set up by autosynthesis; you are merely overriding the default implementation of the property setter.
Are you certain that setting .title property is what you want? If you're in a UINavigationController you may actually want
self.navigationItem.title
instead. Setting the title is independent of source of the title, so I'd work on getting that second View Controller to display a static title you choose, and only then turn to the task of passing a title dynamically from the first VC. Your issue is likely in setting the title string, not in how you pass that string around from VC 1 to VC 2.
As obvious as it seems, the .title property of a UIViewController doesn't always actually cause a title string to be displayed… the rules for when that property is actually used get tricky.
I am assuming that "titleView" is nothing but of type NSString.
In your SecondViewController.h, synthesize your titleView property.
And NSLog it in your SecondViewController before assigning it to self.title just to check that it's getting some value we tried to assign.
Hope this helps!

Need to dynamically create ViewController objects from a tableView selection in iOS

I have a UIViewController in my iOS application that displays a table that is derived from an NSMutableArray. The cells in this table each refer to a unique UIViewController that is called when the user makes a selection. What I am trying to do in my "didSelectRowAtIndexPath:" method is to dynamically create the UIViewController via an NSMutableDictionary that contains keys that match the values in the NSMutableArray that the table is built from, as well as values that contain the corresponding Class names for the respective UIViewController that needs to be called. Because the list is rather long, I figure I need to do this using a for loop, but I am a bit confused as to how to do it. My NSMutableDictionary looks like this:
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:#"aViewController" forKey:#"SelectionA"];
[dict setObject:#"bViewController" forKey:#"SelectionB"];
[dict setObject:#"cViewController" forKey:#"SelectionC"];
and my NSMutableArray that is the basis for my TableView looks like this:
NSMutableArray *myArray = [[NSMutableArray alloc] initWithObjects:#"SelectionA", #"SelectionB", #"SelectionC",...,nil];
How would I obtain a reference to the value inside the cell, and then construct a for loop that would dynamically create the correct viewController that corresponds to the selection made by the user from the tableView, and then take the user to that viewController via the navigationController?
Thanks in advance to all who reply.
Its not a good idea to create many ViewControllers, you should create one ViewController,
and pass the value of they tableCell to it. In other words, you change the data modell of the ViewController, by selecting the cell. But you will present the Same ViewController.
Only in the case that your cells coreesponmd to different types (e.g one cell a road mao, another a text value) , you have to call different ViewControllers.
If you realy need different view contollers, then get the type you want to dispaly from the cell data
in didSelectCellRowAtIndexPath
myAppDelegate *appDelegate =
[[UIApplication sharedApplication] delegate];
UIViewController *viewControllerToDisplay;
switch (selection.type) {
case MapType:
viewControllerToDisplay = appDelegate.mapViewController;
case Picture:
viewControllerToDisplay = appDelegate.pictureViewController;
}
now push viewControllerToDisplay to navigaton Controller.
If you really need a view controller for each cell, there's no need to use a dictionary to look them up. Since selection will be by index path, an array is a better choice.
Create a custom object that has two properties: the name you want to display in the cell and a pointer to the view controller you want to push when it's selected. Load myArray with these objects instead of strings. When you are populating a cell, select the object that matches the row and use its name. When a cell is tapped, select the object that matches the row and push its controller.
(But, as others have said, if you can use the same controller and only change the data, that's the way to go!)
Here is the solution,
If you know the name of class then store all the classes name in array with dictionary having key ClassName and Xib. I prefer plist to store names but you can use other way also.
And at didselect of table or picker place the code like this,
Class classobject = NSClassFromString([[ClassArray objectAtIndex:row]valueForKey:#"ClassName"]);
id object = [[classobject alloc] initWithNibName:[[ClassArray objectAtIndex:row]valueForKey:#"Xib"] bundle:nil];
[self.navigationController pushViewController:object animated:YES];
First line will convert your string to class.
Now as we have a benefit that id can hold any object so creat the object using id. And finally you have a custom class object you can do whatever you want to do with it,here for just a demo I did navigation.

Accessing properties of an object inside array of objects

I have a list of items showing up on a table view.
Every item has its properties such as name, pic, rank etc'.
My goal is, every time the user selects a row the item with its properties will be added to a new list.
I've created a new list called listOfBugs and because i want it to be global i've allocated and initialized it inside viewDidLoad. (Is that a proper thing to do?)
Here is my code:
MasterViewController.h
#interface MasterViewController : UITableViewController
{
NSMutableArray *listOfBugs;
}
#property (strong) NSMutableArray *bugs;
MasterViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
listOfBugs = [[NSMutableArray alloc]init];
self.title = #"Scary Bugs";
}
...
...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
ScaryBugDoc *bug = [self.bugs objectAtIndex:indexPath.row];
UIAlertView *messageAlert = [[UIAlertView alloc]
initWithTitle:#"Row Selected" message:bug.data.title delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[messageAlert show];
[listOfBugs addObject:bug];
NSLog(#"this is %#",listOfBugs);
}
Using NSLog I can see that the objects are added:
ScaryBugs[1195:11303] this is <ScaryBugDoc: 0x75546e0>
2012-12-05 17:45:13.100
ScaryBugs[1195:11303] this is <ScaryBugDoc: 0x75546e0>
I have a few questions.
1.How can I access the properties of the objects inside of the array listOfBugs ?
Update: This worked for me:
NSLog(#"this is %#",((ScaryBugDoc *)[listOfBugs objectAtIndex:0]).data.title);
But I can't access the listOfBugs from another class.
I turned it into a property as suggested to make my life easier but still can't access it from another class.
For example in listOfBugsViewController.m return [_listOfBugs count]; will give me the error Use of undeclared identifier '_listOfBugs'
2.I want to be abale to populate a table view with the customized list, how can i do that?
After accomplishing that I would like to save the list as a plist and also add and remove objects from it at ease so I need to take that under consideration.
This is the code that I'm based on, I only made a few adjustments to create the new list
This is really two questions:
1) How do I make my property a public property which can be accessed by other classes?
You do this just like you did with your bugs property. Add this to your .h file:
#property (strong) NSMutableArray *newList;
Note that if you aren't using different threads, you can make it a little more efficient by using the nonatomic property as well (#property (nonatomic, strong)).
Once you do that, you don't need your iVar declaration because it will automatically be generated for you. (i.e. you can remove NSMutableArray *newList;.)
2) How do I access an object in an array?
Objects in an array are stored as an id object, meaning that it is a "generic" object. If you know what type of object is stored, then you need to tell the compiler what it is so that it knows what properties and methods are appropriate for that class. You do this by casting the variable to the proper type:
ScaryBugDoc *bug = (ScaryBugDoc *)[self.newList objectAtIndex:0];
Then, you can access the properties of the object, assuming that they are public (as covered in point 1 above) like this:
NSLog(#"this is %s", bug.data.tile);
Okay, so based from the comments, this should work:
Album* tempAlbum = [albumList objectAtIndex:i];
//now you can access album's properties
Song* tempSong = [album.songs objectAtIndex:j];
//now you can access song's properties
This can be simplified down to:
Song* someSong = [((Album)[albumList objectAtIndex:i]).songs objectAtIndex:j];
When returning an object from an NSArray, or a collection object like that it will return a generic id object. This will need to be typecasted to the expected object so you can access the right properties.

One IBAction for multiple custom prototype cells with buttons

Using storyboard, I have a table view controller containing multiple dynamic prototype cells. One of the cells is a custom dynamic prototype cell (for which I created a subclass) containing a label and a switch. I have the action for the switch wired to an action method (say switchChanged:) in the view controller. In cellForRowAtIndexPath, I configure and return the cell appropriate for the specified index. So far so good.
The problem: my application has multiple instances of these custom cells, so how do I differentiate which switch has changed? One thought is that in the view controller I can create a member switch for each cell and link them to a specific cell switch when cellForRowAtIndexPath is called for the first time. Then I can use these member switches to compare with the switch that is passed into switchChanged:.
I know that I can try it and get an immediate answer, but I was hoping for a discussion of how other developers do what I am trying to do. Is this the best/worst/ok approach and what are practical alternatives?
Regards,
--John
I had this situation once (not with switches, but I believe it applies just the same). I've managed to get around it by subclassing the object class and adding the required properties/methods inside the subclass.
When calling the action, your sender will be the subclass, and you can access your added code there.
I don't know if it is the case, but if you're only trying to change a value, you should use bind the switch value to the property when creating the object. It will not even need an IBAction to call.
EDIT: Example:
#interface MySwitch : UISwitch
#property (nonatomic, assign) NSUInteger someProperty;
#end
Then, every time you create a cell, you can set "someProperty" to anything you want.
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {
// yada yada yada...
UITableViewCell *cell;
// yada yada yada...
[cell.myLinkedSwitch setSomeProperty:indexPath.row];
return(cell);
}
Unless you're not creating your cells using the tableView:cellForRowAtIndexPath: method. Then you probably should use bindings to get your value to the right place.
Instead of adding a separate subclass, I just stored the row in each button Disabled Title property. This worked very will with little effort. This first code is in the CellForRowAtIndexPath:
NSString *strRow = [[NSString alloc] initWithFormat:#"%i",useRow];
[btnPreferredChk setTitle:strRow forState:UIControlStateDisabled];
Then my action method for the button uses that value to perform the appropriate activity.
- (IBAction)goStorePick:(id)sender
{
UIButton *useButton = [[UIButton alloc] init];
useButton = sender;
NSInteger *storeRow = [[useButton titleForState:UIControlStateDisabled] integerValue];
NSString *CMIMsg = [[NSString alloc] initWithFormat:#"goStorePick Method Executed at Row: %i", storeRow];
[self shwMessage:CMIMsg];
}
This worked well for me.

Enabling and Disabling Annotation Dragging (On The Fly) (iOS Mapkit)

I've created a mapview which has a button to switch between an 'edit' mode and a 'drag' mode based on the project requirements. I realize that it's easy enough to have your annotations draggable from creation by setting them draggable in the viewForAnnotation, but the behavior required won't allow this. I've tried a couple different ways of changing the annotations to draggable without success. The first thought was to loop through the existing annotations and set each one to 'draggable' and 'selected', but I get an unrecognized selector sent to instance error (I did try instantiating a new annotation to pass in the object and re-plot while in the loop, but I get the same error as well):
NSLog(#"Array Size: %#", [NSString stringWithFormat:#"%i", [mapView.annotations count]]);
for(int index = 0; index < [mapView.annotations count]; index++) {
if([[mapView.annotations objectAtIndex:index]isKindOfClass:[locAnno class]]){
NSLog(#"** Location Annotation at Index: %#", [NSString stringWithFormat:#"%i", index]);
NSLog(#"* Location Marker: %#", [mapView.annotations objectAtIndex:index]);
}
if([[mapView.annotations objectAtIndex:index]isKindOfClass:[hydAnno class]]) {
NSLog(#"** Hydrant Annotation at Index: %#", [NSString stringWithFormat:#"%i", index]);
NSLog(#"* Hydrant Marker: %#", [mapView.annotations objectAtIndex:index]);
[[mapView.annotations objectAtIndex:index]setSelected:YES];
[[mapView.annotations objectAtIndex:index]setDraggable:YES];
}
}
The second thought was to use 'didSelectAnnotationView', and set selected and draggable on the annotation when it's selected, and reset the properties when the mode switches back again. This works, but very poorly as the event doesn't always fire and your left to tap the annotation one or more times before it will change the properties:
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
NSLog(#"Annotation Selected!");
if(!editMode) {
view.selected = YES;
view.draggable = YES;
}
}
The first attempt seems the most simple solution if I can get it to work. Using the didSelect method on the other hand is cumbersome and hack-licious. I'm quite new to iOS development, so I apologize if I've overlooked something novice while hammering away at this. I appreciate any insight the community can offer. Thanks much.
The first method is better than using the didSelectAnnotationView delegate method.
The problem with the code which causes the "unrecognized selector" error is that it is calling setSelected: and setDraggable: on the annotation objects (type id<MKAnnotation>) instead of their corresponding MKAnnotationView objects. The id<MKAnnotation> objects don't have such methods so you get that "unrecognized selector" error.
The map view's annotations array contains references to the id<MKAnnotation> (data model) objects -- not the MKAnnotationView objects for those annotations.
So you need to change this:
[[mapView.annotations objectAtIndex:index]setSelected:YES];
[[mapView.annotations objectAtIndex:index]setDraggable:YES];
to something like this:
//Declare a short-named local var to refer to the current annotation...
id<MKAnnotation> ann = [mapView.annotations objectAtIndex:index];
//MKAnnotationView has a "selected" property but the docs say not to set
//it directly. Instead, call deselectAnnotation on the annotation...
[mapView deselectAnnotation:ann animated:NO];
//To update the draggable property on the annotation view, get the
//annotation's current view using the viewForAnnotation method...
MKAnnotationView *av = [mapView viewForAnnotation:ann];
av.draggable = editMode;
You must also update the code in the viewForAnnotation delegate method so that it also sets draggable to editMode instead of a hard-coded YES or NO so that if the map view needs to re-create the view for the annotation after you've already updated it in the for-loop, the annotation view will have the right value for draggable.

Resources