I am initialing a dictionary in viewDidLoad and using it to create table view cells.
The first load works fine. But as soon as I scroll the table view to see the item (not displayed at the bottom) the app crashes. Through the Debugger I noticed the address of the dictionary item "rootItemsDict" changes when I did the scroll. Not able to figure out why that is. From my understanding the address of an object initialized once should remain same, at least within the given class instance. Any thoughts?
- (void)viewDidLoad {
[super viewDidLoad];
NSString *path = [[NSBundle mainBundle] pathForResource:#"menu" ofType:#"plist"];
rootItemsDict = [NSDictionary dictionaryWithContentsOfFile:path];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *title = (NSString *)[[[rootItemsDict objectForKey:#"rootMenuItems"] objectAtIndex:row] objectForKey:#"heading"];
+dictionaryWithContentsOfFile: returns an autoreleased instance. To take ownership you need to explicitly retain it:
rootItemsDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
... or use the alloc/init form:
rootItemsDict = [[NSDictionary alloc] initWithContentsOfFile:path];
... or if you have a suitable property declaration (retain) use the setter:
self.rootItemsDict = [NSDictionary dictionaryWithContentsOfFile:path];
I recommend to read the Memory Management Programming Guide, especially the section on object ownership.
Related
I'm new to iOS dev and I followed a tutorial that was a simple UITableview and a detail view.
This sets up my Array:
- (void)viewDidLoad
{
[self setupArray];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)setupArray
{
states = [[NSMutableDictionary alloc]init];
[states setObject:#"Thing 1" forKey:#"Subject 1"];
[states setObject:#"Thing 2" forKey:#"Subject 2"];
[states setObject:#"Thing 3" forKey:#"Subject 3"];
[states setObject:#"Thing 4" forKey:#"Subject 4"];
datasource = [states allKeys];
}
I have working cells and detail views. How do I add more objects to my keys? Is that possible? I need each subject [key] to have many attributes (i.e. a thing, a person, a place, a color)...
Can you break this down to the most simple terms for me? Thanks!
I'm not sure if I understand your question, but each key can have only one object associated with it. In your case, you're using an NSString object. If you replaced the NSString with some object that you create, say AnObjectWithAThingAndAPersonAndAPlace, you could have multiple attributes associated with each key.
I think I understand what you want now. What you want is not an object with arrays associated to it, but an array of objects. You can do it with NSDictionary objects.
- (void)setupArray
{
NSMutableArray *objectArray = [[NSMutableArray alloc] init];
NSMutableDictionary *object1 = [[NSMutableDictionary alloc] init];
[object1 setObject:#"Apple" forKey:#"thing"];
[object1 setObject:#"Alex" forKey:#"person"];
[object1 setObject:#"Alabama" forKey:#"place"];
[object1 setObject:#"Azure" forKey:#"color"];
[objectArray addObject:object1];
NSMutableDictionary *object2 = [[NSMutableDictionary alloc] init];
[object2 setObject:#"Banana" forKey:#"thing"];
[object2 setObject:#"Bill" forKey:#"person"];
[object2 setObject:#"Boston" forKey:#"place"];
[object2 setObject:#"Blue" forKey:#"color"];
[objectArray addObject:object2];
datasource = [NSArray arrayWithArray:objectArray];
}
Then in your UITableViewDataSource method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger row = [indexPath row];
NSDictionary *object = [datasouce objectAtIndex:row];
...
}
and you can retrieve all the strings for that object.
If I were to do something like this, I would probably create a plist file containing the array. Then your setupArray method could look like this:
- (void)setupArray
{
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"YourFileName" ofType:#"plist"];
NSDictionary *plistData = [NSDictionary dictionaryWithContentsOfFile:filePath];
datasource = (NSArray*)[plistData objectForKey:#"ObjectsForTableView"];
}
I though I would add a few more comments...In case it isn't obvious, the objects you add to your dictionary don't have to be NSStrings, they can be any object, such as an NSNumber, which may be useful for you in the case of your baseball players. Also, you may wish to create a custom player object instead of using an NSDictionary. And you may want to have something like a Core Data database where the players are stored and retrieved (instead of hard coding them or getting them from a plist file). I hope my answer can get you started on the right path though.
RIght now your datasource object is an NSArray. You need to make it an NSMutableArray. Declare it as an NSMutableArray in your header file and then you can do this:
datasource = [[states allKeys] mutableCopy];
[datasource addObject:whatever];
But, it sounds like the structure you are actually looking for is an NSMutableArray of NSDictionary objects. Like this:
NSDictionary *item = [NSDictionary dictionaryWithObjectsAndKeys:#"object1", #"key1", #"object2", #"key2", nil];
[datasource addObject:item]
;
There are numerous ways (better than your given example) to do this. But I'll follow your example.
You are assigning an NSString Object to your keys.
What you can do is create a class Thing that contains all your attributes. and assign an instance of that class to your keys. ie.
[states setObject:myThingObject forKey:#"Subject 4"];
Then pass the myThingObject to your Detail View.
UPDATE:
Thing class contains the following properties:
- person
- place
- color
- thingName
So,
[states setObject:#"Thing 1" forKey:#"Subject 1"];
becomes
[states setObject:firstObject forKey:#"Subject 1"];
[states setObject:secondObject forKey:#"Subject 2"];
Note firstObject & secondObject are instances of your Thing class
To read more about classes in Objective-c visit:
https://developer.apple.com/library/ios/#referencelibrary/GettingStarted/Learning_Objective-C_A_Primer/_index.html
I have a signature page, were the user can sign their name.
It needs to then be saved to NSDictionary, but i want to call a List of the keys to be text in a TableView for each row or cell.
so:
"viewImage = saved as object to key:Random Number"
That parts somewhat easy, the hard part is when i call it on the other Page to the TableView.
It Exits the App with Error"SIGABRT". Now all my Delegates are in place and working...i believe.
now heres some example code:
FirstPage.m
UIImagePNGRepresentation(viewImage);
NSMutableArray *innerArray = [[NSMutableArray array]init];
[innerArray addObject:viewImage];
[SignatureSave setObject:innerArray forKey:#"5599"];
simple Enough, but doesnt give me an error.
SecondPage.m
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
FirstPage *appShare = (FirstPage *)[[UIApplication sharedApplication] delegate];
NSArray *dataDuplicate = [[NSArray alloc]init ];
dataDuplicate = [appShare.SignatureSave allKeysForObject:#"innerArray"];
static NSString *CellIdentifier = #"Cell";
NSLog(#"%#",dataDuplicate);
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero]autorelease];
}
if (dataDuplicate != nil) {
cell.textLabel.text = [dataDuplicate objectAtIndex:indexPath.row];
}
else
{
UIAlertView *CellAlert = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Error Loading content, Try Again Later." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[CellAlert show];
[CellAlert release];
}
return cell;
}
#end
Now, How do i get the viewImage to save to the NSDictionary, to be able to call it on the SecondPage and display the name of the objects in the TableVIew?
I really don't understand whats your problem exactly.
first of all, is your dictionary a retained object?
//FirstPage.h
#property (nonatomic, retain) NSDictionary *SignatureSave;
//FirstPage.m
#synthesize SignatureSave;
…
UIImagePNGRepresentation(viewImage);
NSMutableArray *innerArray = [NSMutableArray array]; // using "array" is equivalent to alloc-init-autorelease
[innerArray addObject:viewImage];
[self.SignatureSave setObject:innerArray forKey:#"5599"];
// OR setting the array directly:
UIImagePNGRepresentation(viewImage);
NSArray *innerArray = [NSArray arrayWithObject:viewImage];
[self.SignatureSave setObject:innerArray forKey:#"5599"];
// OR even setting the image directly to the dictionary:
UIImagePNGRepresentation(viewImage);
[self.SignatureSave setObject:viewImage forKey:#"5599"];
now if you access the object by writing self. in front it will call the retain and your object will stay alive. Otherwise it would be autoreleased at the end of the method. This will fix the problem that your dictionary is maybe not present/available at table view creation and you don't have to use a singleton.
what are you trying to access with this code?
FirstPage *appShare = (FirstPage *)[[UIApplication sharedApplication] delegate];
with [[UIApplication sharedApplication] delegate] you get your application delegate (obviously). These are the MyAppNameAppDelegate files but you treat it as a FirstPage class.
Just NSLog() to check you get the right class, the one you expect.
NSLog(#"%#", [[[UIApplication sharedApplication] delegate] class]);
here you have a potential leak, you alloc-init but never release it:
NSArray *dataDuplicate = [[NSArray alloc]init ];
dataDuplicate = [appShare.SignatureSave allKeysForObject:#"innerArray"];
furthermore you can simplify it (will be autoreleased):
NSArray *dataDuplicate = [appShare.SignatureSave allKeysForObject:#"innerArray"];
and here you have another issue.
Why do you call all keys for the object #"innerArray"?
you don't have such an object and it's in many more cases wrong. innerArray was your previously named array in FirstPage.m but it is only for you as a developer to remember the variable better. After compilation it will have a cryptic name anyway. You could access your key #"5599" if you like but I don't think you want this. In your case you want to access all keys of the dictionary so simply call:
NSArray *dataDuplicate = [appShare.SignatureSave allKeys];
now you will have an array with all keys of your dictionary and you can access them like you do with objectAtIndex:.
NSString *keyName = [dataDuplicate objectAtIndex:indexPath.row];
cell.textLabel.text = keyName;
id theObject = [appShare.SignatureSave objectForKey:keyName]; // for example the image
Tell me if this solves your problems or tell me how I misunderstood your question.
I found the answer to this to be quit simple actually,
I ended up going with the Singleton Method instead of the Global Variable Method.
Now the Singleton Method looks terrifying but its quit simple, See here.
The main difference i noticed from the singleton method to the global method is,
Global method takes a lot of converting and re-converting.
Though the Singleton Method is working with a single object over many pages or classes.
Now i hope this will better assist people in the future also!
I've been attempting to create my own app for a band by my friends, and I've been experimenting with using a custom TableViewCell for news articles that appear on the website. My main objective is to get all of the data from the website, store it in a NSMutableArray, and then display that in my custom cells.
The app runs fine when it loads the first 5-6 cells. However, when I begin to scroll, the app crashes. I've pinpointed the in the cellForRowAtIndexPath: method. Using GDB, I've also come to find out that after I create my NSMutableArray data and once the program runs, after scrolling, my array seems to be autoreleased. I'm not sure why this happens. Here's what I have for my code thus far:
In HomeViewController.h:
#interface HomeViewController : UIViewController {
NSArray *results;
NSMutableArray *titles;
NSMutableArray *dates;
NSMutableArray *entries;
}
#property (nonatomic, retain) NSMutableArray *titles;
#property (nonatomic, retain) NSMutableArray *dates;
#property (nonatomic, retain) NSMutableArray *entries;
#end
In HomeViewController.m:
- (void)viewDidLoad {
[super viewDidLoad];
titles = [[NSMutableArray alloc] init];
dates = [[NSMutableArray alloc] init];
entries = [[NSMutableArray alloc] init];
while((i+1) != endIndex){
NSString *curr_title = [[NSString alloc] init];
NSString *curr_date = [[NSString alloc] init];
NSString *curr_entry = [[NSString alloc] init];
//do some character iterations across a string
[titles addObject:curr_title];
[dates addObject:curr_date];
[entries addObject:curr_entry];
[curr_title release];
[curr_date release];
[curr_entry release];
}
}
//more code here, removed
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"NewsCell";
NewsCell *cell = (NewsCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle]
loadNibNamed:#"NewsCell"
owner:self options:nil];
// cell = [topLevelObjects objectAtIndex:0];
for(id currentObject in topLevelObjects){
if([currentObject isKindOfClass:[UITableViewCell class]]){
cell = (NewsCell *)currentObject;
break;
}
}
}
NSLog(#"%d", indexPath.row);
NSLog(#"%d", titles.count);
cell.cellTitle.text = [titles objectAtIndex:indexPath.row];
cell.datePosted.text = [dates objectAtIndex:indexPath.row];
cell.preview.text = [entries objectAtIndex:indexPath.row];
return cell;
}
Again, the first 5-6 cells show up. Once I scroll, I tried doing po titles and got this error:
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_PROTECTION_FAILURE at address: 0x00000009
0x011b309b in objc_msgSend ()
I've tried allocating the arrays in initWithNibName: but that didn't seem to do much. I've tried moving all the code in viewDidLoad: and then calling [super viewDidLoad] and it just produced the same results as well. Sorry this is so long, but I figured people needed to see my code.
I don't see anything obviously wrong with the code you posted. Try enabling the NSZombieEnabled environment variable. This prevents objects from being released so that when your application "crashes" you can determine which object caused the problem.
Also, instead of looping through array of objects returned by loadNibNamed:owner:options, you should assign the desired object to an IBOutlet property of your class. See Loading Custom Table-View Cells From Nib Files for an example.
The following is extracted from the new code you posted:
NSString *curr_title = [[NSString alloc] init];
//do some character iterations across a string
[titles addObject:curr_title];
[curr_title release];
NSString is not mutable (as is NSMutableString). Are you intentionally adding empty strings to the titles array?
In response to your comment: stringByAppendingString creates a new autoreleased NSString object.
NSString *curr_title = [[NSString alloc] init];
// this leaks the original NSString object (curr_title no longer points to it);
// curr_title now points to a new, autoreleased NSString
curr_title = [curr_title stringByAppendingString:#"..."];
[titles addObject:curr_title];
// releasing the autoreleased NSString will cause your application to crash!
[curr_title release];
*EXC_BAD_ACCESS* is a sure sign that one of your objects is getting over released (or wasn't retained properly). NSZombieEnabled is your friend here, just as titaniumdecoy suggests - figuring out what object is being over released is half the battle. Just be sure to turn it off before releasing the app, because (as titaniumdecoy pointed out) it prevents objects from getting released.
I usually use a combination of NSZombieEnabled and well placed breakpoints (so I can walk through the code till it crashes) to figure out where the problem is cropping up in the code. Then it's usually a simple matter of backtracking to figure out where the object was over released.
The problem might be with the implementation of NewsCell, all your properties there being retained?
Also, any reason HomeViewController is subclassing UIViewController and not UITableViewController?
And this should work just fine:
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"NewsCell" owner:self options:nil];
cell = [topLevelObjects objectAtIndex:0];
I have 2 UITableView in the same View and my question is how to populate them with different data in the viewDidLoad method? thanks.
To answer the plist part of your question:
NSString *filePath=[[[NSBundle mainBundle] pathForResource:#"someName" ofType:#"plist"] retain];
NSDictionary *dict = [[NSDictionary dictionaryWithContentsOfFile:filePath] retain];
So just use two of these, one for each table, then just have two tableView, as it appears you already understand.
You populate them in the
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
and in that function you can compare the tableView to see what table it is, and load the appropriate data for that table.
//Assume the following property
#property (nonatomic, retain) NSDictionary *myData;
add this to the Code file
- (NSDictionary) myData
{
if (myData == nil) //Notice I am accessing the variable, not the property accessor.
{
NSString *filePath=[[NSBundle mainBundle] pathForResource:#"someName" ofType:#"plist"];
myData = [[NSDictionary dictionaryWithContentsOfFile:filePath] retain];
}
return myData;
}
then when you access the self.myData property it will open up if it is not opened already.
All delegate and datasource protocol methods always pass a pointer to the tablesView they are called for. Just do an if to check which is meant in each method.
I've just started using threads and I'm trying to get better performance from my TableView in my app. Each tableViewCell has an imageView and the image is loaded from disk when the tableViewCell is created. I want to load the Image on a differant thread, then set the UIImageView on the main thread. My question is, can a method that is being ran on another thread return a value to the main thread? Is there a better approach for doin this?
Thanks for any help in advance.
maybe something like this, assuming your icons are in the document's directory:
#define DOCUMENTS_DIRECTORY [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]
//inside - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
NSDictionary *d = [NSDictionary dictionaryWithObjectsAndKeys:indexPath, #"indexPath", #"image1.png", #"imageName", nil];
[NSThread detachNewThreadSelector:#selector(loadIcon:) toTarget:self withObject:d];
//
- (void)iconLoaded:(NSDictionary*)dict {
[icons replaceObjectAtIndex:[[dict objectForKey:#"index"] intValue] withObject:[UIImage imageWithData:[dict objectForKey:#"imageData"]]];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[dict objectForKey:#"indexPath"]]];
}
- (void)loadIcon:(NSDictionary*)dict {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *filePath = [DOCUMENTS_DIRECTORY stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.jpg", [dict objectForKey:#"imageName"]]];
NSData *imageData = [NSData dataWithContentsOfFile:filePath];
[self performSelectorOnMainThread:#selector(iconLoaded:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:[dict objectForKey:#"indexPath"], "indexPath", imageData, #"imageData", nil] waitUntilDone:YES];
[pool drain];
}
you will need to keep track of which cells you are loading an image for, so you dont try to load one while it is already loading it. there may be some small syntax errors as i did not compile this, just wrote it freehand.
icons is an NSMutableArray holding a UIImage for each cell
Try this:
https://github.com/foursquare/fully-loaded
Yes, you can pass data to the main thread. See the following method in NSObject API docs:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
You get to pass a single object reference in the arg parameter.