Bronze challenge: another solution


#1

Great book by the way, this is the first time I’ve felt the need to respond to a challenge here as I’ve come up with a solution that everyone else has missed (or not shared :slight_smile:)

I hate reinventing the wheel, so my approach to this was to try to copy what happens with the UIImagePickerController.

It uses callback to notify the caller when an image is picked and return the image. The DetailViewController then takes the image and dismisses the popover.

So I used the same approach for the AssetTypePicker.

First define a protocol (LookupProtocol.h)

[code]@protocol LookupPickerDelegate

  • (void)didChangeSelection:(NSObject*)id;

@end
[/code]

Add delegate property to AssetTypePicker.h and synthesize it in AssetTypePicker.m

//  AssetTypePicker.h
#import <UIKit/UIKit.h>
#import "LookupPickerDelegate.h"

@class BNRItem;

@interface AssetTypePicker : UITableViewController

@property (nonatomic, unsafe_unretained) NSObject <LookupPickerDelegate> *delegate;

@property (nonatomic, strong) BNRItem *item;

@end

Change the didSelectRowAtIndexPath: method in AssetTypePicker.m

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    
    [cell setAccessoryType:UITableViewCellAccessoryCheckmark];
    
    NSArray *allAssets = [[BNRItemStore sharedStore] allAssetTypes];
    NSManagedObject *assetType  = [allAssets objectAtIndex:[indexPath row]];

    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
        
        SEL didChangeSelection = @selector(didChangeSelection:);
        
        //tell my delegate
        if ([[self delegate ] respondsToSelector:didChangeSelection]) {
            [[self delegate] didChangeSelection:assetType];
        }
    } else {
        [item setAssetType:assetType];
        [[self navigationController] popViewControllerAnimated:YES];
    }
}

Add the protocol to DetailViewController.h

#import <UIKit/UIKit.h>
#import "LookupPickerDelegate.h"

@class BNRItem;

@interface DetailViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate, UITextFieldDelegate, UIPopoverControllerDelegate, LookupPickerDelegate>
....

change the method in DetailViewController.m to invoke the popover

- (IBAction)showAssetTypePicker:(id)sender {
    
    if ([assetPickerPopover isPopoverVisible]) {
        [assetPickerPopover dismissPopoverAnimated:YES];
        assetPickerPopover = nil;
        return;
    }
        
    [[self view] endEditing:YES];
    
    AssetTypePicker *assetTypePicker = [[AssetTypePicker alloc] init];
    [assetTypePicker setItem:item];
    [assetTypePicker setDelegate:self];
    
    //place the image picker on the screen
    //check device before putting popover on the screen
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
        
        //create a new popover controller that will display the asset picker
        assetPickerPopover = [[UIPopoverController alloc] initWithContentViewController:assetTypePicker];
        
        [assetPickerPopover setDelegate:self];
        
        //display the popover controller
        // sender is the asset type button item
        CGRect rect =  [assetTypeButton frame];
        
        [assetPickerPopover presentPopoverFromRect:rect
                                            inView:[self view]
                          permittedArrowDirections:UIPopoverArrowDirectionLeft
                                          animated:YES];
    } else {
        [[self navigationController] pushViewController:assetTypePicker
                                               animated:YES];
    }
}

and then implement the delegate method

- (void)didChangeSelection:(NSObject*)id
{
    NSManagedObject *assetType = (NSManagedObject*)id;
    
    [item setAssetType:assetType];
    NSString *typeLabel = [[item assetType] valueForKey:@"label"];
    if(!typeLabel)
        typeLabel = @"None";
    
    [assetTypeButton setTitle:[NSString stringWithFormat:@"Type: %@", typeLabel]
                     forState:UIControlStateNormal];
    
    //take the asset picker off the screen
    // you must call this dismiss method
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
        //dismiss the popover
        [assetPickerPopover dismissPopoverAnimated:YES];
        assetPickerPopover = nil;
    } else {
        [self dismissViewControllerAnimated:YES completion:nil];
    }
}

There is more to be done, a lookup table is fairly common so I would make a generic UILookupController based on UITableViewController, I guess I should implement the setDelegate: so that it checks if the delegate implements the mandatory selector as there is only a warning if you don’t declare that the caller implements the protocol when you do setDelegate:self…

Should change the selection code in AssetTypePicker so that if the caller doesn’t dismiss the popover then the last selected row is the only one that is checked, there is code to do this in another solution here.


#2

How does your popover frame look like?
I tried everything and was unable to set the height of the popover to the content it has, it always goes right to the bottom. The only other thing I could do is to set it fixed with setPopoverContentSize, but then it wouldn’t work if the content goes beyond this size.
For me it looks like this even with your solution:


#3

First off awesome way to do this challenge lennieh! I did it the notification way but then rehashed it with the delegate-protocol way as it makes more sense and seems cleaner to me.

Also titicaca the way I got round the size of the view was to set the the content size to the number of assetTypes * 50 (I think the row default size is 44?) :