Best Practice for Updating information inside a Detail View

Discussion in 'iOS Programming' started by cnstoll, Sep 27, 2010.

  1. cnstoll macrumors 6502

    Joined:
    Aug 29, 2010
    #1
    I'm wondering what the best practice is to modify information that is passed to a detail view controller from a table view controller.

    Basically the way I have this set up is a table view controller gets a list of data objects from the data controller, which it uses to populate it's table cells. Then when a user clicks on a cell the controller pushes a detail view controller with one of the data objects.

    So now the detail view controller has (I believe) an instance of one of the data objects. One of the properties of that object is an NSMutableArray. I want to be able to add/modify elements in that array.

    Currently I have the detail view controller set up to send addObject:element to it's data object's Array...but that's not working. Basically the element get's "added", but if I go back to the table view, and then click on the detail view again, it crashes. I believe that is because the actual object contained by the TableViewController is not being updated.

    So, here's the question:

    Do I need to:

    1) Use the DataController to implement some method to update the object that the TableViewController's object...rather than update the object that the DetailViewController's object directly?

    or

    2) Am I forgetting a step that I need to do to update the information contained in the TableViewController in some other way?

    Thanks
     
  2. Sykte macrumors regular

    Joined:
    Aug 26, 2010
    #2


    What is the message when it crashes sounds to me a simple case of memory manangement. Posting some of the code wouldn't hurt either.
     
  3. cnstoll thread starter macrumors 6502

    Joined:
    Aug 29, 2010
    #3
    I'll try and do that when I get home later.
     
  4. Sykte macrumors regular

    Joined:
    Aug 26, 2010
    #4
    I'm not ignoring your questions, I'm holding off until I see code. I have a feeling it's a mix of memory management and how you're passing the object back and forth.
     
  5. cnstoll thread starter macrumors 6502

    Joined:
    Aug 29, 2010
    #5
    Ok, let's see here...these are what I believe to be the pertinent files... my DetailViewController and the AddViewController (that it presents modally in order to take input for what needs to be added to the Array).

    One thing that immediately occurred to me is that I am sending the addObject message to the array from the Delegate method defined in the DetailViewController rather than in a commitEditingStyle method. Perhaps that is part of the problem...I'm not sure. I set all this up with my TableViewController, along with an AddViewController and a Delegate and everything worked there. But I'll let you look at the code and see for yourself.

    Note: a "Run" is just an object with a NSString "title" property and an NSMutableArray "times" property.


    Code:
    //  RunDetailViewController.m
    
    
    #import "RunDetailViewController.h"
    #import "RunTimeAddViewController.h"
    #import "Run.h"
    
    
    @implementation RunDetailViewController
    
    @synthesize run;
    
    - (id)initWithStyle:(UITableViewStyle)style {
        // Override initWithStyle: if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
        if ((self = [super initWithStyle:style])) {
        }
        return self;
    }
    
     - (void)viewDidLoad {
    	[super viewDidLoad];
    	 
    	UIBarButtonItem *barButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(add:)];
    	self.navigationItem.rightBarButtonItem = barButtonItem;
    	[barButtonItem release];
    }
    
    - (void)viewWillAppear:(BOOL)animated {
    	[super viewWillAppear:animated];
    	
    	// Scroll the table view to the top
    	[self.tableView reloadData];
    	[self.tableView setContentOffset:CGPointZero animated:NO];
    	self.title = run.title;
    }
    
    
    - (void)add:(id)sender
    {
    	/* UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Nothing to add" message:@"Sorry, try again!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
    	 [alertView show];
    	 [alertView release];*/
    	
    	RunTimeAddViewController *addTimeController = [[RunTimeAddViewController alloc] initWithNibName:@"RunTimeAddViewController" bundle:nil];
    	addTimeController.delegate = self;
    	
    	UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:addTimeController];
    	navigationController.navigationBar.barStyle = UIBarStyleBlack;
    	[self presentModalViewController:navigationController animated:YES];
    	
    	[navigationController release];
    	[addTimeController release];
    }
    
    
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    	return 1;
    }
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    	NSInteger rows = 0;
    	rows = [run.times count];
    	return rows;
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    	
    	NSString *cellText = nil;
    	static NSString *CellIdentifier = @"CellIdentifier";
    	
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
        if (cell == nil) {
            cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
            cell.selectionStyle = UITableViewCellSelectionStyleNone;
        }
    	
    	cellText = [run.times objectAtIndex:indexPath.row];
    	
    	cell.textLabel.text = cellText;
    	return cell;
    }
    
    - (void)runTimeAddViewController:(RunTimeAddViewController *)runTimeAddViewController didAddTime:(NSString *)time {
    	if (time != nil) {
    		[run.times addObject:time];
    	}
    	
    	[self dismissModalViewControllerAnimated:YES];
    	[self.tableView reloadData];
    }
    
    - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    	NSString *title = nil;
    	title = @"Times";
    	return title;
    }
    
    - (void)didReceiveMemoryWarning {
        // Releases the view if it doesn't have a superview.
        [super didReceiveMemoryWarning];
        
        // Release any cached data, images, etc that aren't in use.
    }
    
    - (void)viewDidUnload {
        [super viewDidUnload];
        // Release any retained subviews of the main view.
        // e.g. self.myOutlet = nil;
    }
    
    
    - (void)dealloc {
    	[RunTimeAddViewController release];
        [super dealloc];
    }
    
    
    @end
    Code:
    // RunDetailViewController.h
    
    
    #import <UIKit/UIKit.h>
    #import "RunTimeAddViewController.h"
    
    @class Run;
    
    @interface RunDetailViewController : UITableViewController <RunTimeAddDelegate> {
    	Run *run;
    }
    
    @property (nonatomic, retain) Run *run;
    
    @end
    Code:
    //  RunTimeAddViewController.m
    
    #import "RunTimeAddViewController.h"
    
    @implementation RunTimeAddViewController
    
    @synthesize time;
    @synthesize timeTextField;
    @synthesize delegate;
    
    
    
    // Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
    - (void)viewDidLoad {
        self.navigationItem.title = @"Add Time";
    	
    	UIBarButtonItem *cancelButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Cancel" style:UIBarButtonItemStyleBordered target:self action:@selector(cancel)];
    	self.navigationItem.leftBarButtonItem = cancelButtonItem;
    	[cancelButtonItem release];
    	
    	UIBarButtonItem *saveButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Save" style:UIBarButtonItemStyleDone target:self action:@selector(save)];
    	self.navigationItem.rightBarButtonItem = saveButtonItem;
    	[saveButtonItem release];
    	
    	[timeTextField becomeFirstResponder];
    }
    
    - (void)viewDidUnload {
    	self.timeTextField = nil;
    	[super viewDidUnload];
    }
    
    
    
    // Override to allow orientations other than the default portrait orientation.
    - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
        // Return YES for supported orientations
        return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
    }
    
    - (BOOL)textFieldShouldReturn:(UITextField *)textField {
    	if (textField == timeTextField)
    	{
    		[timeTextField resignFirstResponder];
    		[self save];
    	}
    	return YES;
    }
    
    - (void)save {
    	time = timeTextField.text;
    	
    	[self.delegate runTimeAddViewController:self didAddTime:time];
    }
    
    - (void)cancel {
    	[self.delegate runTimeAddViewController:self didAddTime:nil];
    }
    
    - (void)didReceiveMemoryWarning {
        // Releases the view if it doesn't have a superview.
        [super didReceiveMemoryWarning];
        
        // Release any cached data, images, etc that aren't in use.
    }
    
    
    - (void)dealloc {
    	[time release];
    	[timeTextField release];
        [super dealloc];
    }
    
    
    @end


    Code:
    //  RunTimeAddViewController.h
    
    
    #import <UIKit/UIKit.h>
    
    @protocol RunTimeAddDelegate;
    @class Run;
    
    @interface RunTimeAddViewController : UIViewController <UITextFieldDelegate> {
    	@private
    		NSString *time;
    		UITextField *timeTextField;
    		id <RunTimeAddDelegate> delegate;
    }
    
    @property(nonatomic, retain) NSString *time;
    @property(nonatomic, retain) IBOutlet UITextField *timeTextField;
    @property(nonatomic, assign) id <RunTimeAddDelegate> delegate;
    
    - (void)save;
    - (void)cancel;
    
    @end
    
    @protocol RunTimeAddDelegate <NSObject>
    - (void)runTimeAddViewController:(RunTimeAddViewController *)runTimeAddViewController didAddTime:(NSString *)time;
    
    @end
    
     
  6. cnstoll thread starter macrumors 6502

    Joined:
    Aug 29, 2010
    #6
    Found the error:

    2010-09-26 22:52:09.344 WorkoutLog[8953:207] -[__NSArrayM isEqualToString:]: unrecognized selector sent to instance 0x592c580
    2010-09-26 22:52:09.345 WorkoutLog[8953:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayM isEqualToString:]: unrecognized selector sent to instance 0x592c580'
     
  7. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #7
    Somewhere (not, as far as I can tell, in any of the code you've posted) you are calling isEqualToString: on an array. isEqualToString: only works on NSString.
     
  8. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #8
    It could also be calling isEqualToString: on what was an NSString but no longer is, because an unowned NSString hasn't been retained, and was autoreleased.

    Try running the program under Instruments.app, and turn on zombies. If there's an NSString that ends up overreleased and then messaged, zombies should show it.

    http://meandmark.com/blog/2010/09/using-nszombie-with-instruments/
    http://www.corbinstreehouse.com/blo...debug-those-random-crashes-in-your-cocoa-app/
    http://www.cocoadev.com/index.pl?NSZombieEnabled
    http://www.iphonedevsdk.com/forum/iphone-sdk-development/9781-trouble-setting-nszombieenabled.html
    http://www.cocoadev.com/index.pl?DebuggingAutorelease

    Google search terms:
    iphone NSZombie
    iphone NSZombieEnabled
    iphone instruments zombies
     
  9. cnstoll thread starter macrumors 6502

    Joined:
    Aug 29, 2010
    #9
    Well that's part of why that error message makes no sense to me. There's definitely no usage of "isEqualToString" anywhere in the project.
     
  10. Sykte macrumors regular

    Joined:
    Aug 26, 2010
    #10
    I would toss a break point at;
    Code:
    time = timeTextField.text;
    under your save method.
     
  11. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #11
    Yeah, you're right. This is probably much more likely.
     
  12. cnstoll thread starter macrumors 6502

    Joined:
    Aug 29, 2010
    #12
    I have a theory...based off ya'lls comments.

    I think when RunDetailViewController gets the didAddTime:time message that the NSString for "time" is gone. I'm away from my computer so I can't test it yet...but maybe you can tell me if this makes sense.

    I think there are two possible reasons for this.

    1) I release time in RunTimeAddViewController's dealloc method. By the time this gets back to the detail view controller that would probably have been called. So maybe I could remove that line and it would work.

    2) I could create an instance of the NSString where I allocate the RunTimeAddViewController in the detail controller...and set the RunTimeAddViewController's "time" equal to that NSString. That also might fix it.

    What do ya'll think of those possibilities? I apologize for posting this rather than testing it first, but it's helpful enough to me just to think it through by typing the question.
     
  13. cnstoll thread starter macrumors 6502

    Joined:
    Aug 29, 2010
    #13
    Upon further reflection that seems less likely, because the detail view shows the added element after clicking "save" in the modal add view...

    So I guess it could still be a memory management issue with that string, but it's not related directly to didAddTime because that part actually seems to be working.
     
  14. cnstoll thread starter macrumors 6502

    Joined:
    Aug 29, 2010
    #14
    Ha, sweet! It was this in the dealloc method of the addViewController:

    Code:
    [time release];
    I guess it was being released before the caller had a chance to save it. That must have been the problem.

    Thanks for the help.
     

Share This Page