PDA

View Full Version : Delegate Not Called On iPad When Using Popover




ahan.tm
Sep 17, 2012, 10:40 AM
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
@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
- (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
//
// 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
//
// 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:)



pulsewidth947
Sep 17, 2012, 02:56 PM
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?

ahan.tm
Sep 17, 2012, 03:03 PM
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:)

pulsewidth947
Sep 17, 2012, 04:45 PM
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:)

Any particular reason you are declaring the property as weak?

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

Duncan C
Sep 17, 2012, 04:49 PM
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:)

You are correct. You need a line like this:

sharedData.flipsidePopoverController.delegate = self;


I also don't see your code that presents the popover. You need a call to

presentPopoverFromRect:inView:permittedArrowDirections:animated:

or

presentPopoverFromBarButtonItem:permittedArrowDirections:animated:

mfram
Sep 17, 2012, 05:06 PM
Any particular reason you are declaring the property as weak?

According to the ARC videos I watched from the Apple Dev Conference, all delegate properties should be declared weak to prevent reference loops.

pulsewidth947
Sep 17, 2012, 05:11 PM
According to the ARC videos I watched from the Apple Dev Conference, all delegate properties should be declared weak to prevent reference loops.

Interesting. The more you know!

ahan.tm
Sep 17, 2012, 05:48 PM
Any particular reason you are declaring the property as weak?

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

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:)

----------

You are correct. You need a line like this:

sharedData.flipsidePopoverController.delegate = self;


I also don't see your code that presents the popover. You need a call to

presentPopoverFromRect:inView:permittedArrowDirections:animated:

or

presentPopoverFromBarButtonItem:permittedArrowDirections:animated:


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:

- (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:)

PhoneyDeveloper
Sep 17, 2012, 06:23 PM
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.

ahan.tm
Sep 17, 2012, 06:29 PM
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.

I am talking about AddViewController delegate:
- (void)addViewController:(AddViewController *)controller didFinishWithSave:(BOOL)save which is called in the AddViewController.m

The flipsideViewControllerDidFinish is called in MainViewController and works fine.

pulsewidth947
Sep 18, 2012, 01:42 AM
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


// 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];

PhoneyDeveloper
Sep 18, 2012, 07:33 PM
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.

ahan.tm
Sep 18, 2012, 10:04 PM
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.

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. . .

pulsewidth947
Sep 19, 2012, 01:31 AM
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. . .

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.

Duncan C
Sep 19, 2012, 07:06 AM
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. . .

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.

ahan.tm
Sep 21, 2012, 06:39 PM
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.

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:
*** -[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:)

ahan.tm
Sep 22, 2012, 02:48 PM
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!:):)