Delegate Not Called On iPad When Using Popover

Discussion in 'iPhone/iPad Programming' started by ahan.tm, Sep 17, 2012.

  1. macrumors regular

    Joined:
    Jun 26, 2011
    Location:
    Florida
    #1
    Hi,

    I am trying to use a delegate method on a universal app. The delegate method works fine on the iPhone, but not on the iPad. I think it is because the iPad runs the view in a Popover. Here is the code I am using:

    FlipsideViewController.h
    Code:
    @class FlipsideViewController;
    
    @protocol FlipsideViewControllerDelegate
    - (void)flipsideViewControllerDidFinish:(FlipsideViewController *)controller;
    @end
    
    @interface FlipsideViewController : UITableViewController <NSFetchedResultsControllerDelegate, AddViewControllerDelegate, UIPopoverControllerDelegate,MFMailComposeViewControllerDelegate> {
    	NSFetchedResultsController *fetchedResultsController;
    	NSManagedObjectContext *managedObjectContext;
    	NSManagedObjectContext *addingManagedObjectContext;
    
    }
    @property (weak, nonatomic) IBOutlet id <FlipsideViewControllerDelegate> delegate;
    
    @property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
    @property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
    @property (nonatomic, strong) NSManagedObjectContext *addingManagedObjectContext;
    
    - (IBAction)addChild;
    - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath;
    
    - (void)done:(id)sender;
    - (void)helpButton:(id)sender;
    - (void)aboutButton:(id)sender;
    
    @end
    
    FlipsideViewController.m
    Code:
    - (IBAction)addChild {
    
    	AddViewController *addViewController = [[AddViewController alloc] initWithStyle:UITableViewStyleGrouped];
    	
        addViewController.delegate = self;
    
    	// Create a new managed object context for the new book -- set its persistent store coordinator to the same as that from the fetched results controller's context.
    	NSManagedObjectContext *addingContext = [[NSManagedObjectContext alloc] init];
    	self.addingManagedObjectContext = addingContext;
    
    	[addingManagedObjectContext setPersistentStoreCoordinator:[[fetchedResultsController managedObjectContext] persistentStoreCoordinator]];
    
    	addViewController.child = (Child *)[NSEntityDescription insertNewObjectForEntityForName:@"Child" inManagedObjectContext:addingContext];
    
    	if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
    		[self.navigationController pushViewController:addViewController animated:YES];
    
    	}
    	else  {
    
    		UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:addViewController];
    
    		UIBarButtonItem *cancelButton         = [[UIBarButtonItem alloc] initWithTitle:@"Cancel" style:UIBarButtonItemStylePlain target:self action:@selector(done:)];
    
    		navController.navigationItem.leftBarButtonItem = cancelButton;
    
    		addViewController.contentSizeForViewInPopover  = CGSizeMake(320, 480);
    
    		MainViewController *sharedData = [MainViewController sharedMainViewController];
    
    		[sharedData.flipsidePopoverController setContentViewController:navController animated:YES];
    
    	}
    
    }
    
    /**
       Add controller's delegate method; informs the delegate that the add operation has completed, and indicates whether the user saved the new book.
     */
    - (void)addViewController:(AddViewController *)controller didFinishWithSave:(BOOL)save {
    
        NSLog(@"Got IT");
        
    	if (save) {
    		/*
    		   The new book is associated with the add controller's managed object context.
    		   This is good because it means that any edits that are made don't affect the application's main managed object context -- it's a way of keeping disjoint edits in a separate scratchpad -- but it does make it more difficult to get the new book registered with the fetched results controller.
    		   First, you have to save the new book.  This means it will be added to the persistent store.  Then you can retrieve a corresponding managed object into the application delegate's context.  Normally you might do this using a fetch or using objectWithID: -- for example
    
    		   NSManagedObjectID *newBookID = [controller.book objectID];
    		   NSManagedObject *newBook = [applicationContext objectWithID:newBookID];
    
    		   These techniques, though, won't update the fetch results controller, which only observes change notifications in its context.
    		   You don't want to tell the fetch result controller to perform its fetch again because this is an expensive operation.
    		   You can, though, update the main context using mergeChangesFromContextDidSaveNotification: which will emit change notifications that the fetch results controller will observe.
    		   To do this:
    		   1	Register as an observer of the add controller's change notifications
    		   2	Perform the save
    		   3	In the notification method (addControllerContextDidSave:), merge the changes
    		   4	Unregister as an observer
    		 */
    		NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
    		[dnc addObserver:self selector:@selector(addControllerContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:addingManagedObjectContext];
    
    		NSError *error;
    		if (![addingManagedObjectContext save:&error]) {
    			// Update to handle the error appropriately.
    			NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    			//exit(-1);  // Fail
    		}
    		[dnc removeObserver:self name:NSManagedObjectContextDidSaveNotification object:addingManagedObjectContext];
    	}
    
    	// Release the adding managed object context.
    	self.addingManagedObjectContext = nil;
    
    	// Dismiss the view to return to the main list
    	if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
    		[self.navigationController popViewControllerAnimated:YES];
    
    	}
    	else  {
    
    		FlipsideViewController *controller    = [[FlipsideViewController alloc] initWithNibName:@"FlipsideViewController" bundle:nil];
    
    		UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller];
    
    		MainViewController *sharedData        = [MainViewController sharedMainViewController];
    
    		[sharedData.flipsidePopoverController setContentViewController:navController animated:YES];
    
    	}
    
    }
    
    The addChild method gets called to add a new Child and opens the AddViewController. On the iPad, the content view is switched to the AddViewController.

    AddViewController.h
    Code:
    //
    //  AddViewController.h
    //  iBehave
    //
    //  Created by Ahan Malhotra on 10/8/11.
    //  Copyright (c) 2011 Random Widgets. All rights reserved.
    //
    #import "DetailViewController.h"
    
    @protocol AddViewControllerDelegate;
    
    @interface AddViewController : DetailViewController 
    
    @property (nonatomic, weak) id <AddViewControllerDelegate> delegate;
    
    - (IBAction)cancel:(id)sender;
    - (IBAction)savechild:(id)sender;
    
    @end
    
    @protocol AddViewControllerDelegate
    - (void)addViewController:(AddViewController *)controller didFinishWithSave:(BOOL)save;
    @end
    
    AddViewController.m
    Code:
    //
    //  AddViewController.m
    //  iBehave
    //
    //  Created by Ahan Malhotra on 10/8/11.
    //  Copyright (c) 2011 Random Widgets. All rights reserved.
    //
    
    #import "AddViewController.h"
    #import "Child.h"
    #import "FlipsideViewController.h"
    
    @implementation AddViewController
    
    @synthesize delegate;
    
    #pragma mark View lifecycle
    
    - (void)viewDidLoad {
    
    	[super viewDidLoad];
    	// Override the DetailViewController viewDidLoad with different navigation bar items and title.
    	self.title                             = @"New Child";
    	self.navigationItem.leftBarButtonItem  = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
    	                                                                                       target:self action:@selector(cancel:)];
    	self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave
    	                                                                                       target:self action:@selector(savechild:)];
    
    	// Set up the undo manager and set editing state to YES.
    	[self setUpUndoManager];
    	self.editing = YES;
    
    }
    
    - (void)viewDidUnload {
    	[super viewDidUnload];
    	// Release any properties that are loaded in viewDidLoad or can be recreated lazily.
    	[self cleanUpUndoManager];
    }
    
    #pragma mark Save and cancel operations
    
    - (IBAction)cancel:(id)sender {
        
        //FlipsideViewController *flipsideViewController = [FlipsideViewController alloc];
    	
        //[flipsideViewController.self addViewController:self didFinishWithSave:NO];
    }
    
    - (IBAction)savechild:(id)sender {
    
        NSLog(@"Save");
        
    	if (child.name == nil) {
    		UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:@"Please Enter A Value In The Name Field." delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil];
    		[alert show];
    		return;
    	}
    
        
        
    	[self.delegate addViewController:sender didFinishWithSave:YES];
    
        
        
    }
    
    @end
    
    In the end, the savechild method in AddViewController.m is called, but it never calls the delegate method.

    Where is the problem?

    Thanks:)
     
  2. macrumors 65816

    pulsewidth947

    Joined:
    Jan 25, 2005
    Location:
    squarefrog.co.uk
    #2
    Which delegate isn't getting called? The only line I see is:

    addViewController.delegate = self; in FlipsideViewController.m. Have you made sure to set the other delegates you have delcared?
     
  3. thread starter macrumors regular

    Joined:
    Jun 26, 2011
    Location:
    Florida
    #3
    Hi,

    The delegate that is not called is the AddViewControllerDelegate. I think my problem is because I am not setting the delegate properly in FlipsideViewController, but I am not sure. . .

    Thanks:)
     
  4. macrumors 65816

    pulsewidth947

    Joined:
    Jan 25, 2005
    Location:
    squarefrog.co.uk
    #4
    Any particular reason you are declaring the property as weak?

    Perhaps a silly question, but did you synthesize the delegate in FlipsideViewController.m ?
     
  5. macrumors 6502a

    Duncan C

    Joined:
    Jan 21, 2008
    Location:
    Northern Virginia
    #5
    You are correct. You need a line like this:

    Code:
    sharedData.flipsidePopoverController.delegate = self;
    
    I also don't see your code that presents the popover. You need a call to

    Code:
    presentPopoverFromRect:inView:permittedArrowDirections:animated:
    
    or

    Code:
    presentPopoverFromBarButtonItem:permittedArrowDirections:animated:
    
     
  6. macrumors 6502a

    Joined:
    Jan 23, 2010
    Location:
    San Diego, CA USA
    #6
    According to the ARC videos I watched from the Apple Dev Conference, all delegate properties should be declared weak to prevent reference loops.
     
  7. macrumors 65816

    pulsewidth947

    Joined:
    Jan 25, 2005
    Location:
    squarefrog.co.uk
    #7
    Interesting. The more you know!
     
  8. thread starter macrumors regular

    Joined:
    Jun 26, 2011
    Location:
    Florida
    #8
    The property is declared as weak because the ARC conversion tool set it as weak.

    I do not synthesize the AddViewControllerDelegate in FlipsideViewController.m . Do I need to? It is only synthesized in the AddViewController.m .

    This code runs perfectly normal on the iPhone. . .

    Thanks:)

    ----------

    I added your line of code, but it does not work. I present the Popover in a MainViewController.m

    Here is some code from my MainViewController.m:

    Code:
    - (IBAction)showInfo:(id)sender //Flipside
    {
    	FlipsideViewController *controller = [[FlipsideViewController alloc] initWithNibName:@"FlipsideViewController" bundle:nil];
    	controller.delegate = self;
    
    	UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller];
    
    	if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { //iPhone
    
    		[self presentViewController:navController animated:YES completion:nil];
    
    	}
    	else  { //iPad
    		if (!self.flipsidePopoverController) {
    
    			self.flipsidePopoverController = [[UIPopoverController alloc] initWithContentViewController:navController];
    			[flipsidePopoverController setDelegate:self];
    		}
    		if (self.flipsidePopoverController.popoverVisible == YES) {
    			[self fetchData];
    
    			if ([records count] > 0) { //If Any Rows In Records Array
    
    				name.text          = [[records objectAtIndex:pageControl.currentPage] valueForKey:@"name"];
    				prize.text         = [[records objectAtIndex:pageControl.currentPage] valueForKey:@"prize"];
    				color              = [[[records objectAtIndex:pageControl.currentPage] valueForKey:@"color"] intValue];
    				marbles            = [[[records objectAtIndex:pageControl.currentPage] valueForKey:@"marbles"] intValue];
    				marblesneeded      = [[[records objectAtIndex:pageControl.currentPage] valueForKey:@"marblesneeded"] intValue];
    				marblesleft        = marblesneeded - marbles;
    				marblesNeeded.text = [NSString stringWithFormat:@"%i", marblesleft];
    
    				recordsempty       = NO;
    			}
    			else  {
    				recordsempty = YES; ///Records Empty causes UIAlertView in viewDidLoad
    			}
    
    			if ([records count] > 1) {
    				[pageControl setAlpha:1]; //If multiple children, them display pageControl
    			}
    			else  {
    				[pageControl setAlpha:0]; //If Single Child, Then Hide PageControl
    			}
    
    			[self setColor];
    
    			if (recordsempty == NO) { //Prevents crash if marbles appear without any records
    				[self filjar1];
    			}
    
    			[self.flipsidePopoverController dismissPopoverAnimated:YES];
    		}
    		else  {
    			[self.flipsidePopoverController presentPopoverFromRect:[info bounds] inView:info permittedArrowDirections:UIPopoverArrowDirectionDown animated:YES];
    		}
    	}
    
    }
    Thanks:)
     
  9. macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #9
    It seems that your delegate property is an IBOutlet so you might be expected to set its value in the nib/storyboard. Or you could set it in code. Up to you.

    There is a single method in your @protocol : flipsideViewControllerDidFinish: I don't see any code that you showed where it's called. If that's what you're asking about then you need to call this method somewhere.
     
  10. thread starter macrumors regular

    Joined:
    Jun 26, 2011
    Location:
    Florida
    #10
    I am talking about AddViewController delegate:
    Code:
    - (void)addViewController:(AddViewController *)controller didFinishWithSave:(BOOL)save
    which is called in the AddViewController.m

    The flipsideViewControllerDidFinish is called in MainViewController and works fine.
     
  11. macrumors 65816

    pulsewidth947

    Joined:
    Jan 25, 2005
    Location:
    squarefrog.co.uk
    #11
    You don't actually call the delegate method in your FlipsideViewController. I was expecting to see something like:
    [delegate addViewController: aViewController didFinishWithSave: YES];

    Where does this call take place? I see it in AddViewController:
    [self.delegate addViewController:sender didFinishWithSave:YES];

    ----------

    Here is my cheat sheet to setting up and using the delegate pattern

    Code:
    // ParentClass.h
    #import "ChildClass.h"
    @interface ParentClass : NSObject <ChildDelegate>
    
    
    
    
    // ParentClass.m
    // Alloc and init your child class, then:
    childClass.delegate = self;
    
    // Make sure your method is set up in the parent
    -(void)someAction {
      // Do something
    }
    
    
    
    // ChildClass.h
    @protocol ChildDelegate <NSObject>
    - (void)someAction;
    @end
    
    @interface {
    	id <ChildDelegate> delegate;
    }
    @property (nonatomic, weak) id <ChildDelegate> delegate;
    @end
    
    
    
    
    ChildClass.m
    @synthesize delegate;
    
    // Somewhere in one of your methods call the delegate method
    [self.delegate someAction];
    
     
  12. macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #12
    You don't seem to keep a strong reference to the addViewController. I think you need a strong property for it in your flipSideViewController. I think what happens is that you create the addViewController but since you don't keep a strong reference it is dealloced when it goes out of scope.
     
  13. ahan.tm, Sep 18, 2012
    Last edited: Sep 18, 2012

    thread starter macrumors regular

    Joined:
    Jun 26, 2011
    Location:
    Florida
    #13
    Hi Everyone! Thanks for your wonderful responses! I think I have figured out the problem(somewhat:)) . When I use the weak property, it doesn't work. When I use the strong property, it works! But, then it crashes completely. Is there a way around this? Should I just exclude these files from ARC?

    This now makes sense because when I converted my project to ARC, then this problem began to happen. . .
     
  14. macrumors 65816

    pulsewidth947

    Joined:
    Jan 25, 2005
    Location:
    squarefrog.co.uk
    #14
    Does it give you an error in the log when it crashes? Might be worth including exception breakpoints. Click on the Breakpoints tab, then at the bottom click the plus, and click on Add Exception Breakpoint.
     
  15. macrumors 6502a

    Duncan C

    Joined:
    Jan 21, 2008
    Location:
    Northern Virginia
    #15
    Where does it crash, and what is the error message? And as the other poster said, if you're not seeing a crash line that is useful, try adding an exception breakpoint, and dragging the detail slider in the debug navigator to the highest level of detail.
     
  16. ahan.tm, Sep 21, 2012
    Last edited: Sep 21, 2012

    thread starter macrumors regular

    Joined:
    Jun 26, 2011
    Location:
    Florida
    #16
    Hey everyone! Thanks for you replies, they are greatly appreciated. I keep on getting an EXC_BAD_ACCESS error. When using Zombies, here is what is returned:
    Code:
     *** -[FlipsideViewController respondsToSelector:]: message sent to deallocated instance 0xab26390
    This occurs when I try to exit the popover.

    It seems that MainViewController is getting released. Is there a way to fix that?

    Thanks:)
     
  17. thread starter macrumors regular

    Joined:
    Jun 26, 2011
    Location:
    Florida
    #17
    Hi Everyone!! I fixed it!!!

    It seems that I was setting the flipsidePopoverController.delegate to self. This was causing it to get deallocated. Now everything works smoothly(Hopefully).

    Thanks for all you responses!:):)
     

Share This Page