Bronze Challenge: holy CRAP that was hard! my solution


#1

I must have missed something, or approached this the wrong way, or over-implemented… because I just spent literally 4 hours playing with this challenge to get it how I wanted. =/

[size=110]Goals:[/size]
(1) Present the AssetTypePicker in a UIPopoverController - duh
(2) Follow proper MVC-store architecture
(3) User behavior like other iPhone/iPad apps (that is, it looks good and does what you’d expect)

[size=110]My journey:[/size]
[ul][li]First (Failed) Approach: After some SERIOUS hunting around, my first ‘solution’ to this problem was to simply dismiss the UIPopoverController in a forceful way. I solved this by creating variables WITHIN the AssetTypePicker class to both DetailViewController and its assetTypePickerPopover variable, but setting these pointers in the DetailViewController class. I used these pointers to pass the variables controllers commands such as [popoverController dismissPopoverAnimated:YES]…

Two problems I had with this approach: first, using dismissPopoverAnimated: seems to bypass all of the UIPopoverControllerDelegate protocols - not good. Second, it left me in a weird spot, trying to refresh or clear the DetailViewController’s view from within the AssetTypePicker’s class. This didn’t feel very MVC! So I scrapped this approach in the end and kept hunting.[/li][/ul]
[ul][li]Second (Successful) Approach: This one took me about 2 hours to really figure out. I decided that I would rely on the user tapping off the AssetTypePicker pop-up to save the selection. I settled here because that is pretty Apple-ish, AND it conforms to and kicks off the proper UIPopoverControllerDelegate protocols, so I could perform all of the my post-selection clean-up from within the DetailViewController class - and that feels very MVC. So, let’s see how I did it…[/li][/ul]

[size=110]Solution Outline:[/size]
(Step 1) Add the assetTypePickerPopover variable to the DetailViewController class
(Step 2) Amend showAssetTypePicker: in the DetailViewController class to include iPad-specific instructions
(Step 3) Now that the picker is displayed, you’re dealing with row selection. This was very challenging for me - my code below with comments.
(Step 4) After the user taps off the popover, the change is saved and popoverControllerDidDismissPopover: is sent back to its delegate (DetailViewController) - so its back there to implement the final cleanup!

That’s it. I’m hoping/praying there is an easier way to handle this operation, since it seems so common and foundational. Would love any feedback/thoughts/things I missed or forgot. Enjoy!

[size=125]Step 1: DetailViewController.h[/size]

//basically you just need to add this variable: ... UIPopoverController *assetTypePickerPopover; ...

[size=125]Step 2: DetailViewController.m[/size]

[code]- (IBAction)showAssetTypePicker:(id)sender {
[[self view] endEditing:YES];

AssetTypePicker *assetTypePicker = [[AssetTypePicker alloc] init];
[assetTypePicker setItem:item];

if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
    
    // iPad instructions:
    // Create a new popover controller to display the content
    UIPopoverController *atpp = [[UIPopoverController alloc] initWithContentViewController:assetTypePicker];
    assetTypePickerPopover = atpp;
    
    // Set assetTypePicker 'popoverController' to atpp and 'controller' to DetailViewController
    [assetTypePicker setPopoverController:assetTypePickerPopover];
    [assetTypePicker setController:self];
    
    // Set the delegate to DetailViewController
    [assetTypePickerPopover setDelegate:self];
    
    // Get CGRect for assetTypeButton (needed to anchor the popover)
    CGRect frame = [assetTypeButton frame];
    
    CGRect rect = CGRectMake(frame.origin.x,
                             frame.origin.y, 
                             frame.size.width, 
                             frame.size.height);
    
    // Display the popover controller; sender is the button
    [assetTypePickerPopover presentPopoverFromRect:rect 
                                            inView:[self view] 
                          permittedArrowDirections:UIPopoverArrowDirectionAny 
                                          animated:YES];
} else {
    
    // iPhone instructions:
    [[self navigationController] pushViewController:assetTypePicker
                                           animated:YES];
}

}[/code]

[size=125]Step 3: AssetTypePicker.m (ignore the logging - it was just for my own debugging)[/size]

[code]- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

NSLog(@"new selection == %@", indexPath);

// Step 1: Find the old selection
NSArray *allAssets = [[BNRItemStore sharedStore] allAssetTypes];
NSIndexPath *oldSelection = [NSIndexPath indexPathForRow:[allAssets indexOfObject:[item assetType]] inSection:0];
NSLog(@"oldSelection == %@", oldSelection);

UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:oldSelection];
NSLog(@"oldCell == %@", oldSelection);

// Step 2: Uncheck the old selection
[oldCell setAccessoryType:UITableViewCellAccessoryNone];

// Step 3: Find the new selection
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];

// Step 4: Check the new selection
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];

// Step 5: Commit the change via setAssetType:
NSManagedObject *assetType = [allAssets objectAtIndex:[indexPath row]];

[item setAssetType:assetType];

// Step 6: Dismiss the page if on iPhone; if iPad, we will let user dismiss/save by tapping away
// (note: tapping away calls popoverControllerDidDismissPopover: in the delegate, i.e. DetailViewController)
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
    
    // iPhone instructions:
    [[self navigationController] popViewControllerAnimated:YES];
}

}[/code]

[size=125]Step 4: DetailViewController.m (again!)[/size]

[code]- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
NSLog(@“User dismissed popover”);
imagePickerPopover = nil;

// Set the assetTypePickerPopover to nil
assetTypePickerPopover = nil;

// Reset the button text directly
NSString *typeLabel = [[item assetType] valueForKey:@"label"];
[assetTypeButton setTitle:[NSString stringWithFormat:@"Type: %@", typeLabel]
                 forState:UIControlStateNormal];

}[/code]