Challenge: New Asset Types Popover Question


#1

Hi,

I finished the challenge to show the AssetTypePicker view in a PopoverView.
But I have a question: when I select a cell (assetType) in the tableView, in the PopoverView, I would like the popover to dismiss immediately.
So I would like the method: “-(void)tableView:(UITableView *)tv didSelectRowAtIndexPath:(NSIndexPath *)ip” in AssetTypePicker to trigger a method in ItemDetailViewController.
The UIPopoverController “assetTypePickerPopoverController” is in ItemDetailViewController.m and I have no idea how to reach ItemDetailViewController from AssetTypePicker, so I can send the dismissMyPopover: message.

in ItemDetailViewController i added this method for the removal of the popoverview:

-(void)dismissMyPopover
{
    NSLog(@"dismissMyPopover");
    [assetTypePickerPopoverController dismissPopoverAnimated:YES];
}

But now i need to trigger this method from the AssetTypePicker view in the popover view.
I hope someone can help me along with this.

What i did to create the popoverView:
The first three items are not necessary for the popover, but I need it to dismiss the popover (I think)
in ItemDetailViewController.h I added:

    UIPopoverController *assetTypePickerPopoverController;

and

@property (nonatomic, retain) UIPopoverController *assetTypePickerPopoverController;

in ItemDetailViewController.h I added:

@synthesize assetTypePickerPopoverController;

in ItemDetailViewController.h I changed method: - (IBAction)showAssetTypePicker:(id)sender

- (IBAction)showAssetTypePicker:(id)sender 
{
    [[self view] endEditing:YES];
    AssetTypePicker *assetTypePickerView = [[AssetTypePicker alloc] init];
    [assetTypePickerView setPossession:possession];
    
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
        // for the iPad
        // Setup the popover for use in the popup view.
        assetTypePickerPopoverController = [[UIPopoverController alloc] initWithContentViewController:assetTypePickerView];
        [assetTypePickerPopoverController setPopoverContentSize:CGSizeMake(220., 320.)];
        [assetTypePickerPopoverController setDelegate:self];
        
        // Present the popover from the button that was tapped in the detail view.
        [assetTypePickerPopoverController presentPopoverFromRect:[sender frame] 
                                                          inView:self.view 
                                        permittedArrowDirections:UIPopoverArrowDirectionAny 
                                                        animated:YES];
    } else {
        // for all other (iPhone, iPodTouch)
        [[self navigationController] pushViewController:assetTypePickerView animated:YES];
    }
}

and changed/added in popoverControllerDidDismissPopover:

-(void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController
{
    NSLog(@"User dismissed popover");
    if ([[popoverController contentViewController] isKindOfClass:[UIImagePickerController class]]) {
        [imagePickerPopover autorelease];
        imagePickerPopover = nil;
    }
    if ([[popoverController contentViewController] isKindOfClass:[AssetTypePicker class]]) {
        NSString *typeLabel = [[possession assetType] valueForKey:@"label"];
        if (!typeLabel) {
            typeLabel = @"none";
        }
        [assetTypeButton setTitle:[NSString stringWithFormat:@"%@", typeLabel] forState:UIControlStateNormal];
    }
}

#2

Hi,

So you could follow the same pattern that is used in ItemDetailViewController and define a protocol and a delegate.

[code]//AssetTypePicker.h
@protocol AssetTypePickerDelegate

  • (void)dismissMyPopover;
    @end

@interface AssetTypePicker : UITableViewController {

}

@property (nonatomic, assign) id assetTypeDelegate;
[/code]

This is defining a protocol so that any class that claims to be an AssetTypePickerDelegate must implement a dismissMyPopover method.
It then creates a new delegate property assetTypeDelegate (avoiding the name “delegate” as we don’t want to confuse it with the UITableView delegate)

When the user selects a row in the popover - tell the assetTypedelegate to perform the dismissMyPopover method.

[code]//AssetTypePicker.m

@synthesize assetTypeDelegate;

  • (void)tableView:(UITableView *)tableView
    didSelectRowAtIndexPath:(NSIndexPath *)ip
    {

    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
    [[self assetTypeDelegate] dismissMyPopover];
    else
    [[self navigationController] popViewControllerAnimated:YES];
    }
    [/code]

Then make the ItemDetailViewController conform to this protocol:

//ItemDetailViewController.h
@interface ItemDetailViewController : UIViewController 
    <UINavigationControllerDelegate, ... AssetTypePickerDelegate>

And set the delegate when creating the object:

- (IBAction)showAssetTypePicker:(id)sender { ... [assetTypePickerView setPossession:possession]; [assetTypePickerView setAssetTypeDelegate:self];

and finally in your dismissMyPopover method we need to tell the viewController that the popover was dismissed (if you tap outside the popover this is called for you but because we are dismissing it programatically we need to do this ourselves)

-(void)dismissMyPopover { NSLog(@"dismissMyPopover"); [assetTypePickerPopoverController dismissPopoverAnimated:YES]; [self popoverControllerDidDismissPopover:assetTypePickerPopoverController]; }

I think that’s it.

Note that you don’t have to define a protocol to get this to work but it’s a good habit to get into.

HTH
Gareth


#3

Hi Gareth,

Thank you very much for helping me out again.
Your advice/code has again clarified a lot.
I even managed to have a navigation bar with button at the top of the popover, to create new asset types.
And everything works like a charm, programming is fun!

I have just one question:
you say:

I agree with you on good programming but I am curious how you would accomplish the same without the protocol definition.

Sicco


#4

Thanks - glad it worked out.

What I meant was that you could remove the protocol declaration altogether and make the assetTypeDelegate a plain old NSObject without the
You would still set the delegate and call the dismissPopOver method exactly as before and everything will still work. You may get a warning that NSObject may not respond to dismissPopover but you could cast it - [(ItemDetailViewController *)[self assetTypeDelegate] dismissMyPopover]; and get rid of the warning.
Or you could define the assetTypeDelegate as an ItemViewController and wouldn’t need the cast.

But either way you’ve started to create hard-wired dependencies in the code between ItemViewController and AssetTypePicker and this makes for a less flexible architecture. When it’s only one method it’s not a big deal but if the relationship depended on a number of methods being present (like UITableViewDelegate) then it really starts to pay dividends.

HTH
Gareth


#5

Again thank you for your quick and clear reply.

Sicco.


#6

I followed GarethR’s delegation approach and it worked like a charm. Thanks a ton!

I did have one issue where adding the protocol name to the ItemDetailViewController seems to raise an error saying "Can’t find protocol declaration for ‘AssetTypePickerDelegate’ even though its defined. I even tried adding:

[code]#import <UIKit/UIKit.h>
#import “AssetTypePicker.h” # <-- Added

@class Possession;
@class AssetTypePicker; # <-- Added[/code]

But this didn’t help. When I remove the protocol name, it removes the error and I am able to compile and run the code and it works fine. Does anyone know why this would happen? Is there a limit of the number of protocols I can add?


#7

Hi,

There isn’t a practical limit to the number of protocols so it looks like the compiler is not recognising the protocol definition for some reason.

Your AssetTypePicker.h should look something like this:

#import <UIKit/UIKit.h>
@class Possession;
@protocol AssetTypePickerDelegate <NSObject>
- (void)dismissMyPopover;
@end

@interface AssetTypePicker : UITableViewController {
    Possession *possession;
}
@property (nonatomic, retain) Possession *possession;
@property (nonatomic, assign) id <AssetTypePickerDelegate> assetTypeDelegate;
@end

and in ItemDetailViewController.h

...
#import "AssetTypePicker.h"
...
@interface ItemDetailViewController : UIViewController 
    <UINavigationControllerDelegate, ..., ..., AssetTypePickerDelegate>
{

If you start typing AssetType… in that @interface line does Xcode autocomplete for you ?

HTH
Gareth


#8

It does autocomplete, but it gets flagged as an error when I build. Let me fiddle with the code format and see if that helps. I even tried a clean and build and still no luck. Thanks for the assist.


#9

Hi, did you ever figure out why the AssetTypeDelegate was not being recognised when compiling. I’m getting exactly the same issue as dbadev. Everything seems to be set up as above and when adding the delegate to ItemDetailViewController it autocompletes but throws up the error “Cannot find protocal declaration for ‘AssetTypeDelegate’” and won’t compile.

Just wondering if you dbadev ever worked out what was going on and how they got round it?


#10

Hi,

Wondering if my instructions are missing something.

If you could post your AssetTypePicker.h and ItemDetailViewController.h files I’ll have a look.

Gareth


#11

Thanks Gareth,

My AssetTypePicker.h

[code]#import <Foundation/Foundation.h>
#import “ItemDetailViewController.h”

@class Possession;

@protocol AssetTypePickerDelegate

  • (void)dismissAssetTypePickerPopover;
    @end

@interface AssetTypePicker : UITableViewController {
Possession *possession;
UITextField *myTextField;
}

@property (nonatomic, retain) Possession *possession;
@property (nonatomic, assign) id assetTypePickerDelegate;

  • (void)addAssetType:(id)sender;

@end[/code]

and ItemDetailViewController.m

[code]#import <Foundation/Foundation.h>
#import “AssetTypePicker.h”

@class Possession;
@class ItemDetailViewController;

@protocol ItemDetailViewControllerDelegate

@optional

  • (void)itemDetailViewControllerWillDismiss:(ItemDetailViewController *)vc;

@end

@interface ItemDetailViewController : UIViewController
<UIImagePickerControllerDelegate, AssetTypePickerDelegate, UINavigationControllerDelegate, UITextFieldDelegate, UITabBarDelegate, UIPopoverControllerDelegate> {

IBOutlet UITextField *nameField;
IBOutlet UITextField *serialNumberField;
IBOutlet UITextField *valueField;
IBOutlet UILabel *dateLabel;
IBOutlet UIImageView *imageView;
IBOutlet UIBarButtonItem *imageTrashButton;
IBOutlet UIButton *assetTypeButton;

Possession *possession;

UIPopoverController *imagePickerPopover;
UIPopoverController *assetTypePickerPopover;

}

  • (id)initForNewItem:(BOOL)isNew;

@property (nonatomic, retain) Possession *possession;
@property (nonatomic, assign) id delegate;

  • (IBAction)takePicture:(id)sender;
  • (IBAction)backgroundTapped:(id)sender;
  • (IBAction)removePicture:(id)sender;
  • (IBAction)showAssetTypePicker:(id)sender;

@end
[/code]

Thanks for taking a look!
Toby


#12

Hi,

Can’t see anything wrong there.

I notice in this line:

that you called the variable assetTypePickerDelegate whereas I called it assetTypeDelegate

and the required method is dismissAssetTypePickerPopover vs dismissMyPopover

but as long as you changed it in all the right places in the .m file this would’t make any difference.

Could you post the .m files as well - We will solve this :slight_smile:

Gareth


#13

Hi Gareth

Thanks for taking the time to look over my code. I did however have some luck with this and managed to figure out what was going on.

It seems I had a recursive imports issue i.e. I was importing the ItemDetailViewController.h in the AssetTypePicker.h file and visa versa, so I there must have been some infinite loop going on which was causing the compiler to throw up the "“Cannot find protocal declaration for …” error.

To get round it I left the #import “AssetTypePicker.h” in the ItemDetailViewController.h.
But I removed #import “ItemDetailViewController.h” from AssetTypePicker.h and instead used a forward class declaration as below:

@class ItemDetailViewController;

Worked a treat and now complies without complaint!

Thanks again for you help.

Toby


#14

Hi,

Nice one - glad it worked out.

Gareth