Gold Challenge solution


#1

Hello, this is my solution. I found some pretty complicated tutorials on how to create custom backgrounds and arrows etc. but I’ve just gone with the least imaginative way of doing it.

First you have to subclass UIPopoverBackgroundView, as it doesn’t work on it’s own. I called my subclass; BNRCustomPopoverBackgroundView.
There are some properties and methods that are required to be overridden. My BNRCustomPopoverBackgroundView.m looks like this:

#import "BNRCustomPopoverBackgroundView.h"

@implementation BNRCustomPopoverBackgroundView

@synthesize arrowDirection  = _arrowDirection;
@synthesize arrowOffset     = _arrowOffset;

+ (CGFloat)arrowBase
{
    return 0;
}

+ (CGFloat)arrowHeight
{
    return 0;
}

+ (UIEdgeInsets)contentViewInsets
{
    return UIEdgeInsetsMake(3.0,3.0,3.0,3.0);
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        self.backgroundColor = [UIColor greenColor];
    }
    return self;
}
@end

Then I implement my custom background like this in BNRDetailViewController.m:

#import "BNRCustomPopoverBackgroundView.h"
...

- (IBAction)takePicture:(id)sender
{
 ...
    if([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
        ...
        self.imagePickerPopover.popoverBackgroundViewClass = [BNRCustomPopoverBackgroundView class];
        ...
    } else {
        ...
    }
}

#2

I agree that the doc is not that easy to follow. Here are some of my findings and questions.

  • explicit ‘synthesize’ is necessary for arrowDirection and arrowOffset.

  • they seem set by sdk, so it is enough to initialize to UIPopoverArrowDirectionUnknown and 0 in initializer.

  • we can set background color or image.

  • not sure what wantsDefaultContentAppearance method is for.

  • not sure how to place my custom arrow image between popover and camera button since y position is not clear; x position is ‘center.x + arrowOffset’

+(CGFloat)arrowHeight
{
    return 15;
}

+(UIEdgeInsets)contentViewInsets
{
    UIEdgeInsets edgeInsets = UIEdgeInsetsMake(0, 0, [BNRPopoverBackgroundView arrowHeight], 0);
    return edgeInsets;
}

#3

I agree – the documentation was more difficult to understand on this class than the norm.
Here is how I got mine to work (although I’m still unclear on a few things – see bottom).

Create a new subclass of type UIPopoverBackgroundView.

[quote]BNRPopoverBackgroundView.h

[code]@interface BNRPopoverBackgroundView : UIPopoverBackgroundView

@property (nonatomic, readwrite) UIPopoverArrowDirection arrowDirection;
@property (nonatomic, readwrite) CGFloat arrowOffset;

@end[/code][/quote]

[quote]BNRPopoverBackgroundView.m

[code]#import “BNRPopoverBackgroundView.h”

@implementation BNRPopoverBackgroundView

@synthesize arrowDirection = _arrowDirection;
@synthesize arrowOffset = _arrowOffset;

  • (id)initWithFrame:(CGRect)frame
    {
    self = [super initWithFrame:frame];
    if (self) {
    self.backgroundColor = [UIColor redColor];
    self.arrowDirection = UIPopoverArrowDirectionUp;
    self.arrowOffset = 7.0;
    }
    return self;
    }

  • (void)setArrowDirection:(UIPopoverArrowDirection)arrowDirection
    {
    _arrowDirection = arrowDirection;
    return;
    }

  • (void)setArrowOffset:(CGFloat)arrowOffset
    {
    _arrowOffset = arrowOffset;
    return;
    }

  • (CGFloat)arrowBase
    {
    return 20.0;
    }

  • (CGFloat)arrowHeight
    {
    return 50.0;
    }

  • (UIEdgeInsets)contentViewInsets
    {
    return UIEdgeInsetsMake(3.0,3.0,3.0,3.0);
    }

  • (BOOL)wantsDefaultContentAppearance
    {
    return NO;
    }
    [/code][/quote]

And add this line to the takePicture: method in BNRDetailViewController.m after you initialize the UIPopoverController

Although this code works, some questions I’m perplexed by:

[*]Why does it crash unless I explicitly synthesize the instance vars of both properties for this class? Both properties are readwrite by default, and I only implemented the setters.

[*]Why do I have to implement the setters manually? It doesn’t work otherwise, but all I did was assign the instance variables. It’s strange because both properties are built-in by the class type.


#4

[quote=“bryanr”]I agree – the documentation was more difficult to understand on this class than the norm.
Here is how I got mine to work (although I’m still unclear on a few things – see bottom).

Create a new subclass of type UIPopoverBackgroundView.

[quote]BNRPopoverBackgroundView.h

[code]@interface BNRPopoverBackgroundView : UIPopoverBackgroundView

@property (nonatomic, readwrite) UIPopoverArrowDirection arrowDirection;
@property (nonatomic, readwrite) CGFloat arrowOffset;

@end[/code][/quote]

[quote]BNRPopoverBackgroundView.m

[code]#import “BNRPopoverBackgroundView.h”

@implementation BNRPopoverBackgroundView

@synthesize arrowDirection = _arrowDirection;
@synthesize arrowOffset = _arrowOffset;

  • (id)initWithFrame:(CGRect)frame
    {
    self = [super initWithFrame:frame];
    if (self) {
    self.backgroundColor = [UIColor redColor];
    self.arrowDirection = UIPopoverArrowDirectionUp;
    self.arrowOffset = 7.0;
    }
    return self;
    }

  • (void)setArrowDirection:(UIPopoverArrowDirection)arrowDirection
    {
    _arrowDirection = arrowDirection;
    return;
    }

  • (void)setArrowOffset:(CGFloat)arrowOffset
    {
    _arrowOffset = arrowOffset;
    return;
    }

  • (CGFloat)arrowBase
    {
    return 20.0;
    }

  • (CGFloat)arrowHeight
    {
    return 50.0;
    }

  • (UIEdgeInsets)contentViewInsets
    {
    return UIEdgeInsetsMake(3.0,3.0,3.0,3.0);
    }

  • (BOOL)wantsDefaultContentAppearance
    {
    return NO;
    }
    [/code][/quote]

And add this line to the takePicture: method in BNRDetailViewController.m after you initialize the UIPopoverController

Although this code works, some questions I’m perplexed by:

[*]Why does it crash unless I explicitly synthesize the instance vars of both properties for this class? Both properties are readwrite by default, and I only implemented the setters.

[*]Why do I have to implement the setters manually? It doesn’t work otherwise, but all I did was assign the instance variables. It’s strange because both properties are built-in by the class type.[/quote]

Why are you redeclaring the properties that the superclass declares? I haven’t tested the code, but I think your code might work if you remove the properties, @synthesize lines, and the setter implementations. After all, those are all already declared and implemented in the superclass.


#5

then you should give it a try…

can’t get it to work without the @synthesize statements.

@synthesize arrowDirection = _arrowDirection; @synthesize arrowOffset = _arrowOffset;

and you are correct, the setter methods are not needed.


#6

I agree, I got everything except the synthesizers. Once I added the synthesizers, it started working.


#7

The documentation of UIPopoverBackgroundView says the default implementation of the methods associated with these 2 properties will raise exceptions. So I think in the original class of UIPopoverBackgroundView the accessors of these 2 properties have been already implemented so the instance variables associated to them will not be generated.
I guess that’s why we have to synthesise the instance variables ourselves when subclassing.