Annotations and Table Views - objective-c

I am trying populate my table view using my array of annotations but XCode seems to give me a breakpoint any time I add this code.
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
NSMutableArray *annotations = [[NSMutableArray alloc] init];
if(indexPath.section == 0)
{
for(Location *annotation in [(MKMapView *)self annotations])
{
if(![annotation isKindOfClass:[MKUserLocation class]])
{
}
}
cell.textLabel.text = [[annotations objectAtIndex:indexPath.row] title];
}
return cell;
My annotations:
CLLocationCoordinate2D thecoordinate59;
thecoordinate59.latitude = 51.520504;
thecoordinate59.longitude = -0.106725;
Location *ann1 = [[Location alloc] init];
ann1.title =#"Antwerp";
ann1.coordinate = thecoordinate1;
NSMutableArray *annotations = [NSMutableArray arraywithObjects: ann.. ann59, nil];
[map addAnnotations:annotations];

In cellForRowAtIndexPath, you are declaring a new, local variable named annotations which has nothing to do with the annotations array you are creating in viewDidLoad (I assume that's where you're adding the annotations).
Then in cellForRowAtIndexPath, this line:
for(Location *annotation in [(MKMapView *)self annotations])
fails because there is no annotations property in self. In viewDidLoad, you declared a local variable named annotations but it is not visible or accessible outside that method.
The other issue with the above line is that you're casting self as an MKMapView *. Most likely self is a UIViewController. It contains a map view but is not itself one.
You need to first declare your annotations array at the detail view's class level so it's available across all methods. In the detail view .h file:
#property (nonatomic, retain) NSMutableArray *annotations;
By the way, I'd name it something different so it's not confused with the map view's own annotations property.
In the .m, synthesize it:
#synthesize annotations;
In viewDidLoad, create it like this:
self.annotations = [NSMutableArray arraywithObjects...
[map addAnnotations:self.annotations];
In the numberOfRowsInSection method, return the array's count:
return self.annotations.count;
Then in cellForRowAtIndexPath:
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
if(indexPath.section == 0)
{
cell.textLabel.text = [[self.annotations objectAtIndex:indexPath.row] title];
}
return cell;

You are reporting a crash (i assume) on that line
the problem that I see is crash reason would be
cell.textLabel.text = [[annotations objectAtIndex:indexPath.row] title];
annotations array is just initialized locally few statements above which does not hold any values..??

Related

Saving state of UITableView cell accessory?

I have gesture recognisers set up on my table view.
Swipe to the right and the accessory changes to an image of a tick
Swipe to the left and is changes to a chevron image
If a cell is tapped, it loads a local HTML file.
If you swipe to the right, the tick appears as it should. However, if you then tap a cell to view a HTML file and come back to the table view, the image reverts to the chevron.
What's the best way to ensure the tick stays as it should?
EDIT
Further code:
From 'viewDidLoad':
UISwipeGestureRecognizer *recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self
action:#selector(handleSwipeRight:)];
[recognizer setDirection:(UISwipeGestureRecognizerDirectionRight)];
[self.tableView addGestureRecognizer:recognizer];
recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self
action:#selector(handleSwipeLeft:)];
//recognizer.delegate = self;
[recognizer setDirection:(UISwipeGestureRecognizerDirectionLeft)];
[self.tableView addGestureRecognizer:recognizer];
- (void)handleSwipeLeft:(UISwipeGestureRecognizer *)gestureRecognizer
{
//Get location of the swipe
CGPoint location = [gestureRecognizer locationInView:self.tableView];
//Get the corresponding index path within the table view
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
//Check if index path is valid
if(indexPath)
{
//Get the cell out of the table view
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
//Update the cell or model
cell.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"disclosure.png"]];
}
}
- (void)handleSwipeRight:(UISwipeGestureRecognizer *)gestureRecognizer
{
CGPoint location = [gestureRecognizer locationInView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
if(indexPath)
{
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
// cell.accessoryType = UITableViewCellAccessoryCheckmark;
cell.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"tick.png"]];
}
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"MFGCell";
MFGCell *cell = (MFGCell *) [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MFGCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
cell.itemTitle.text = [item objectAtIndex:indexPath.row];
cell.itemDescription.text = [description objectAtIndex:indexPath.row];
cell.itemImageView.image = [UIImage imageNamed:[icons objectAtIndex:indexPath.row]];
return cell;
}
In reaction to the user's swipe you should store the user's choice (e.g. in a private instance variable of type NSMutableArray). When the user comes back to the table view you can then reuse the information in your tableView:cellForRowAtIndexPath: to setup the cell with the correct accessory style.
Property declaration:
#property(nonatomic, retain) NSMutableArray* _accessoryStyle;
Synthesize the property. Then add this snippet to the bottom of handleSwipeLeft: to store the user's choice:
- (void)handleSwipeLeft:(UISwipeGestureRecognizer *)gestureRecognizer
{
[...]
NSNumber* number = [numberWithInt:0];
[_accessoryStyle replaceObjectAtIndex:indexPath.row withObject:number];
}
Add a similar snippet to the bottom of handleSwipeRight::
- (void)handleSwipeRight:(UISwipeGestureRecognizer *)gestureRecognizer
{
[...]
NSNumber* number = [numberWithInt:1];
[_accessoryStyle replaceObjectAtIndex:indexPath.row withObject:number];
}
In tableView:cellForRowAtIndexPath::
NSString* accessoryImageName;
NSNumber* number = [_accessoryStyle objectAtIndex:indexPath.row];
switch ([number intValue])
{
case 0:
accessoryImageName = #"disclosure.png";
break;
case 1:
accessoryImageName = #"tick.png";
break;
default:
// replace with your error handling code
return nil;
}
cell.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:accessoryImageName]];
For all this to work you need to initialize the _accessoryStyle array with the same number of elements that you expect your table view to have cells. For instance, in your view controller's viewDidLoad:
- (void) viewDidLoad
{
[super viewDidLoad];
self._accessoryStyle = [NSMutableArray arrayWithCapacity:0];
NSNumber* defaultAccessoryStyle = [numberWithInt:0];
int numberOfRows = 17; // get the real number from somewhere
for (int index = 0; index < numberOfCells; ++index)
[_accessoryStyle addObject:defaultAccessoryStyle];
}
And to balance this you need to add
- (void) viewDidUnload
{
[super viewDidUnload];
self._accessoryStyle = nil;
}
There is still much room for improvement:
Find better variable names
Use an enumeration for the different styles instead of just hardcoded numbers 0 and 1
Do not allocate a new UIImageView for each table view cell, just allocate two of them and use the right one depending on the accessory style
For your problem, there is an underlying logic issue because there is either a swipe left event firing when it should not or the views are just being unloaded and resetting to default. See if you can log when the events fire; otherwise the state of the view should be preserved. Also what I would do is add an extra state variable like int currentCellState that you change when you enter your different states to keep track of your states. Then in your viewDIdLoad make sure that all your data and your view are in sync, ie the value of currentCellState matches the state of your view.
The best way to do this is to put the images/buttons you have in an array, and each time the view loads it shows the item which index is selected..
in order to do this, the swipeMethode should be modified to something like this
-(void)swipeMethod: (UISwipeGestureRecognizer *) sender
{
if(sender.direction ==
UISwipeGestureRecognizerDirectionLeft && index < [myArray count]){
[self setSelectedIndex:index+1 animated:YES];
index++;
}else if (sender.direction == UISwipeGestureRecognizerDirectionRight && index > 0) {
[self setSelectedIndex:index-1 animated:YES];
index--;
}else {
return;
}
}
in the viewDidLoad add this code:
leftRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipeMethod:)];
[leftRecognizer setDirection: UISwipeGestureRecognizerDirectionLeft];
[self.tableView addGestureRecognizer:leftRecognizer];
rightRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipeMethod:)];
[rightRecognizer setDirection: UISwipeGestureRecognizerDirectionRight];
[self.tableView addGestureRecognizer:rightRecognizer];

Access variables from another class

I'm a beginner at Objective-C programming and I need to access data stored in a NSMutableArray from another class for populating a TableView, however I only get null.
The variables I need to access are in the class below:
FunctionsController.h
#import <UIKit/UIKit.h>
#interface FunctionsController : UIView {
#public NSMutableArray *placesNames;
NSMutableArray *placesAdresses;
NSMutableArray *placesReferences;
NSMutableArray *placesLatitudes;
NSMutableArray *placesLongitudes;
NSArray *list;
}
#end
In this other class I'm trying to access the data but I only get null as result.
SimpleSplitController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
FunctionsController *arrays = [[FunctionsController alloc] init];
NSMutableArray *names = [arrays->placesNames];
// Set up the cell...
cell.textLabel.text = [names objectAtIndex:indexPath.row];
cell.textLabel.adjustsFontSizeToFitWidth = YES;
cell.textLabel.font = [UIFont systemFontOfSize:12];
cell.textLabel.minimumFontSize = 10;
cell.textLabel.numberOfLines = 4;
cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
return cell;
}
The problem is here:
FunctionsController *arrays = [[FunctionsController alloc] init];
NSMutableArray *names = [arrays->placesNames];
First you are allocating the FunctionsController again. That gives you a clean new instance with no data in its variables. If that 'init' is not putting that in those variables you aren't gonna get anything from them.
A second problem I see is that you are accessing the variable directly. I would use properties instead. You declare a property by doing this in your FunctionsController.h:
#property (nonatomic, retain) NSMutableArray *placesNames;
And adding this to your FunctionsController.m:
#synthesize placesNames;
And then you access the property by doing this:
NSMutableArray *names = arrays.placesNames;
Finally I would recommend you to use Core Data for storing that data instead because it seems that it should belong to a SQL database. More about Core Data here: http://developer.apple.com/library/ios/#DOCUMENTATION/DataManagement/Conceptual/iPhoneCoreData01/Introduction/Introduction.html
Here's your problem:
FunctionsController *arrays = [[FunctionsController alloc] init];
NSMutableArray *names = [arrays->placesNames];
Unless you're setting up placesNames in the init method of FunctionsController then it will either be empty or nil.
Take a look at singletons on objective-c.

Initialize NSMutableArray

Im very new in xcode 3 and i really need help for this
I developed my apps using UITableView and XML to showed the content.
i had 3 .xib files which it rootViewController , SecondViewController and mainview.
So the problem is:
When i try to executed didSelectrow in rootViewController and access the NSMutableArray *array in SecondViewController and replace the *array value with new array value in rootViewController before pushed animation.
The array value on my SecondViewController was changed for the first time but when i pushed the back button and select the other row, my SecondViewController array kept read the previous array not change to a new one. I try to initialize but no luck
This is my code on rootViewController UITableview (didSelectRow):
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if(2ndController == nil)
2ndController = [[DetailViewController alloc] initWithNibName:#"SecondViewController" bundle:[NSBundle mainBundle]];
//Declare xml NSMutable array record
ListRecord *record = [self.entries objectAtIndex:indexPath.row];
//access SecondViewController NSMutable *record
2ndController.record = [[[NSMutableArray alloc] init] autorelease];
//inserting the value from firstview to secondview before push
2ndController.record = record;
//push animation
[self.navigationController pushViewController:2ndController animated:YES];
}
This is my second view controller :
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
switch(indexPath.section)
{
case 0:
[cell setText:record.name];
break;
case 1:
[cell setText:record.Age];
break;
case 2:
[cell setText:record.summary];
break;
}
return cell;
}
Hope someone can help me..
Thanks in advance.....
Few things,
You do,
2ndController.record = [[[NSMutableArray alloc] init] autorelease];
and follow it up with
[cell setText:record.name];
Clearly, the record property doesn't seem to be an instance of NSMutableArray so I think the array initialization part is incorrect as you do, and already mentioned,
2ndController.record = record;
But the problem I think is that you are retaining your UITableViewController subclass. Have you tried reloading the data?
[self.tableView reloadData];
Add it in the viewWillAppear method of your DetailViewController.
You should look at these two lines again:
2ndController.record = [[[NSMutableArray alloc] init] autorelease];
//inserting the value from firstview to secondview before push
2ndController.record = record;
The first line doesn't do anything useful for you. It creates and initializes a new NSMutableArray and sets the record property to that new array.
But then in the very next line you set the same 'record' property again to a different object and so that array in the first line is no longer referenced. So you might as well not have ever created it.
That's not your issue exactly, but this comment was too big for a comment. :)

NSTableView don't display data

I have data in NSMutableArray and I want to display it in NSTableView, but only the number of cols has changed.
This use of NSTableView is based on tutorial here.
FinalImageBrowser is IBOutlet to NSTableView.
#implementation AppController
NSMutableArray *listData;
- (void)awakeFromNib {
[FinalImageBrowser setDataSource:self];
}
- (IBAction)StartReconstruction:(id)sender
{
NSMutableArray *ArrayOfFinals = [[NSMutableArray alloc] init]; //Array of list with final images
NSString *FinalPicture;
NSString *PicNum;
int FromLine = [TextFieldFrom intValue]; //read number of start line
int ToLine = [TextFieldTo intValue]; //read number of finish line
int RecLine;
for (RecLine = FromLine; RecLine < ToLine; RecLine++) //reconstruct from line to line
{
Start(RecLine); //start reconstruction
//Create path of final image
FinalPicture = #"FIN/final";
PicNum = [NSString stringWithFormat: #"%d", RecLine];
FinalPicture = [FinalPicture stringByAppendingString:PicNum];
FinalPicture = [FinalPicture stringByAppendingString:#".bmp"];
[ArrayOfFinals addObject:FinalPicture]; // add path to array
}
listData = [[NSMutableArray alloc] init];
[listData autorelease];
[listData addObjectsFromArray:ArrayOfFinals];
[FinalImageBrowser reloadData];
NSBeep(); //make some noise
NSImage *fin = [[NSImage alloc] initWithContentsOfFile:FinalPicture];
[FinalImage setImage:fin];
}
- (int)numberOfRowsInTableView:(NSTableView *)tv {
return [listData count];
}
- (id)tableView:(NSTableView *)tv objectValueFromTableColumn:(NSTableColumn *)tableColumn row:(int)row {
return (NSString *)[listData objectAtIndex:row];
}
#end
When the StartReconstruction end the number of cols have changed right, but they're empty. When I debug app, items in listData is rigth.
I'm guessing you forgot to connect the FinalImageBrowser outlet to the table view. That would mean your setDataSource: message is to nil, which would leave the table view without a data source.
You don't need to send that message anyway—you can set the data source in the nib. Remove your awakeFromNib implementation and connect the table view's dataSource outlet to your data source object in IB, as well as the FinalImageBrowser outlet to the table view (also in IB).

Memory leak with returned NSMutableArray from function

I'm having problems with the implementation of the memory management of objective c. I've reed several manuals, but i think that I'm loosing something.
I'm developing an application that uses a UITableView that is loaded with the results of a query;
-(NSMutableArray *)sqlReader:(NSString *)sqlString
{
NSMutableArray *sqlResult = [[[NSMutableArray alloc] init] autorelease];
NSMutableArray *sqlRow = [[NSMutableArray alloc] init];
sqlite3 *database;
int dataType;
int intResult;
int colCount;
int a;
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK)
{
const char *sqlStatement = [sqlString UTF8String];
sqlite3_stmt *compiledStatement;
if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK)
{
[sqlResult removeAllObjects];
while(sqlite3_step(compiledStatement) == SQLITE_ROW)
{
//[sqlRow removeAllObjects];
sqlRow = [[NSMutableArray alloc] init];
colCount = sqlite3_data_count(compiledStatement);
for (a=0;a<colCount;a++)
{
dataType = sqlite3_column_type(compiledStatement, a);
if (dataType == SQLITE_INTEGER)
{
intResult = sqlite3_column_int(compiledStatement, a);
[sqlRow addObject:[NSString stringWithFormat:#"%d",intResult]];
}
else if (dataType == SQLITE_TEXT)
{
[sqlRow addObject:[NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, a)]];
}
else if (dataType == SQLITE_BLOB)
{
NSData *dataForCachedImage = [[NSData alloc] initWithBytes:sqlite3_column_blob(compiledStatement, a) length: sqlite3_column_bytes(compiledStatement, a)];
[sqlRow addObject:[UIImage imageWithData:dataForCachedImage]];
[dataForCachedImage release];
}
}
[sqlResult addObject:sqlRow];
[sqlRow release];
}
return sqlResult;
}
}
return nil;
}
The class that contains this function is used from a different class that is where the delegate methods of UITableView are implemented.
In the UIViewController of this class:
in the .h file is declared:
NSMutableArray *loadedInfo;//will contain all query results
#property (nonatomic, retain) NSMutableArray *loadedInfo;
in implementation file .m
#synthesize loadedInfo;
- (void)viewDidLoad {
loadedInfo = [[NSMutableArray alloc] init];
//do other initializations
In other method is where loadedInfo is filled with the query results:
-(void)loadTemas
{
loadedInfo = [SQL sqlReader:#"SELECT * FROM TEMAS ORDER BY NOMBRETEMA;"];
//This returns an autoreleased NSMutableArray
[detail reloadData];
//This calls the delegate methods of UITableView
}
The following two methods are the delegate of UITableView and a method used to create the returned cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (searchMode == 0)
return [self setTemaCell:indexPath tableView:tableView];
else if (searchMode == 1)
return [self setItemCell:indexPath tableView:tableView];
}
#pragma mark Set Cell Functions
-(UITableViewCell *)setTemaCell:(NSIndexPath *)indexPath tableView:(UITableView *)tableView
{
UITableViewCell *customCell;
UIImageView *imgIcon;
UILabel *lblTitle;
customCell = [tableView dequeueReusableCellWithIdentifier:#"CellID"];
CGRect frame = CGRectMake(0, 0, 0, 20);
//if (!customCell)
//{
customCell = [[[UITableViewCell alloc] initWithFrame:frame reuseIdentifier:#"CellID"] autorelease];
//}
//customCell = [customCell initWithFrame:frame reuseIdentifier:#"CellID"];
customCell.selectionStyle = UITableViewCellSelectionStyleNone;
imgIcon = [[UIImageView alloc] initWithFrame:CGRectMake(10, 13, 64, 64)];
[imgIcon setImage:[self imageWithImage:[[loadedInfo objectAtIndex:indexPath.row] objectAtIndex:2] scaledToSize:CGSizeMake(64, 64)]];
//This is the line where I'm having an EXEC_BAD_ACCESS.
[customCell.contentView addSubview:imgIcon];
[imgIcon release];
imgIcon = nil;
lblTitle = [[[UILabel alloc] initWithFrame:CGRectMake(80, 30, 270, 20)] autorelease];
lblTitle.font = [UIFont systemFontOfSize:24.0];
lblTitle.textAlignment = UITextAlignmentLeft;
lblTitle.backgroundColor= [UIColor whiteColor];
lblTitle.textColor = [UIColor blackColor];
lblTitle.text = [[loadedInfo objectAtIndex:indexPath.row] objectAtIndex:1];
[lblTitle setAlpha:1];
[customCell.contentView addSubview:lblTitle];
[customCell setAlpha:1];
return customCell;
}
I've readed the Memory Management Programing guide and several posts where explain the rules. Well, if i follow the rules (When an object must be returned from a method, this have to be declared with autorelease) the app crashes but if I don't the app memory occupation is higher than 30 MB!
My target is create a function where
(NSMutableArray *)function_A
{
NSMutableArray *theArray = [[NSMutableArray alloc] init];
/fill the array
return [theArray autorelease];
}
Caller Class
in header declare an array
NSMutableArray *theArray;
in init function initialize the array
theArray = [[NSMutableArray alloc] init];
in fill function fill the array with returned array
theArray = [myclass function_A];
in diferent functions use this returned array
in dealloc function release the array.
Is this possible? How is the best way to implement this?
Any help will be apreciated.
It looks like in your caller class you forget to release the old theArray object, and you should retain it.
Easiest would be to declare theArray in your caller class as a retained property (or copied), and when setting, use the provided setter:
self.theArray = [myclass function_A];
which will invoke the correct memory management behaviour.
Don't forget to release in dealloc.
Returning an autoreleased object is fine and what one should expect with that method name.