Gold challenge question


#1

I have worked through the Gold challenge to the point that the image is centered within the popover but have a question on if I have attacked this in the most sensible fashion.

My initial thought was that since the ImageViewController was a “child” of the popover controller I would expose two properties in the popover controller that define the popover width and height. These properties would then be used in the ImageViewController to define the frame for the image view. Something like this:

My problem came when I tried to find a way to get from ImageViewController to the popover controller that was its container. I could not seem to find a property of the ImageViewController that got me back to the popover. I tried a variety of properties including

parentViewController
presentingViewController
navigationController

…in each case the attempt to reference the popover controller via one of these came back with null.

What I ended up doing to address this is to create a custom initializer for the ImageViewController that is passed a width and height at initialization:

- (id)initWithPopoverWidth:(float)pWidth popoverHeight:(float)pHeight { self = [super initWithNibName:nil bundle:nil]; if (self) { popoverWidth = pWidth; popoverHeight = pHeight; } return self; }

I then use these parameters when setting the frame for the image:

This certainly works as the ImageViewController now knows the size of the popover and “centers” the image nicely. I have not tested with large images to make sure my math covers that use case but that is not germane to the question :wink:

Am I missing something or is there really no good way for the ImageViewController to access the popover as its parent/containing view?


#2

I think the trouble you’re having is that you’re doing that inside viewWillAppear, right? I believe at that point, the popover doesn’t exist yet, so there are no values for it to pass.

In any case, I just hard-coded the values for the popover into imageViewController as well, since they’re already hard-coded into the former.

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear];
    
    CGSize sz = [[self image] size];
    [scrollView setMinimumZoomScale:1];
    [scrollView setMaximumZoomScale:10];
    [scrollView setDelegate:self];
    CGSize container = CGSizeMake(600, 600);
    [scrollView setContentSize:sz];
    [imageView setFrame:CGRectMake((container.width - sz.width)/2, (container.height - sz.height)/2, sz.width, sz.height)];
    [imageView setImage:[self image]];
}

Perhaps Joe had something else in mind for us…


#3

I realize that I could certainly use hard coded values in the solution but the developer in me hates hard coded values as now I would have to modify 2 classes over a simple change in dimensions of the popover. I was just trying to figure out what an experienced iOS dev would do to pass data between the popover controller and its view controller. If anyone has any thoughts on a different way to solve that other than a custom init in the contained view controller please let me know.


#4

Check out UIImage’s size property and UIViewController’s contentSizeForViewInPopover property.


#5

I’m struggling with this too. I tried to use contentSizeForViewInPopover but the imagePopover overrides the size without changing the property of the ImageViewController.

The challenge sounds like you should be able to only make changes to the ImageViewController.m, which makes that solution and the one the OP posted not correct, am I right? Otherwise you have to do what the OP suggested or you have to do something like:

[[imagePopover contentViewController] setContentSizeForViewInPopover:CGSizeMake(600, 600)]; [imagePopover setPopoverContentSize:CGSizeMake(600, 600)];


#6

From ImageViewController’s viewWillAppear method, when I get the value of contentSizeForViewInPopover, the size I get is (320, 1100), which is listed as the default size for popoverContentSize if you don’t set it. But, this value was set in the ItemsViewController, showImage:atIndexPath: method to 600x600. The popover even appears on screen at 600x600. Is viewWillAppear too early to have the accurate content size for the popover?


#7

Is this okay???

[code]- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear];

CGSize sz = [[self image] size];
[scrollView setMinimumZoomScale:1.0];
[scrollView setMaximumZoomScale:10.0];
[scrollView setDelegate:self];

[scrollView setContentSize:sz];
[imageView setFrame:CGRectMake(300 - sz.width / 2, 300 - sz.height / 2, sz.width, sz.height)];
[imageView setImage:[self image]];

}

  • (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
    {
    return imageView;
    }[/code]


#8

So any development on how to go about getting the popover size?

@Nerburikun I don’t think I am the one to judge but I did it pretty much exactly as you did with small differences, such as my max zoom scale being 5.0 and also I think it is easier to read the code if you write the part with setFrame, as ((container.size.width - sz.width)/2…), so that it would read (600 - sz.width) / 2, however the way you did it is correct.

By the way I thought this gold challenge was particularly easy, does anyone agree? :open_mouth:


#9

Hi

I don’t know if I understood Joe’s instructions correctly, but I tried to solve this challenge as to “fix” something I didn’t like about the popover : the image was displayed in its original size, thus looking small in the popover and having white borders all around.
Therefore, I used the technique presented by Joe in the book to calculate a scaling ratio, but this time, making it bigger to display it without any border around, and center it.
The idea is to compare the size of the image itself (property “size” of UIImage) with the frame in which it will appear in the popover (property contentSizeForViewInPopover of UIViewController) : adjust to the max ratio popover / image (should be over 1), and use this ratio to scale the image up to fill the popover (in fact, the smaller side of the image must go up to 600).

And then allow to zoom in / out of the image with a pinch gesture.

Here is what was declared in ImageViewController.h

[code]#import <UIKit/UIKit.h>

@interface ImageViewController : UIViewController

{
__weak IBOutlet UIScrollView *scrollView;
__weak IBOutlet UIImageView *imageView;
}

@property (nonatomic, strong) UIImage *image;
@end
[/code]

Here is what was implemented in ImageViewController.m

[code]- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

  • (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear];

    CGSize sz = [[self image]size];
    [scrollView setContentSize:sz];

    // Define the min and max zoom scale of the scrollView
    [scrollView setMinimumZoomScale:1.0];
    [scrollView setMaximumZoomScale:10.0];

    // Needed to declare that ImageViewController conforms to the UIScrollViewDelegate protocol
    [scrollView setDelegate:self];

    // ContentSizeForViewInPopover is by default (320,1000)
    [self setContentSizeForViewInPopover:CGSizeMake(600, 600)];

    // popoverContentSize should be (600,600) now
    CGSize pcSize = [self contentSizeForViewInPopover];

    float ratio = MAX(pcSize.width / sz.width,
    pcSize.height / sz.height);

    float newWidth = ratio * sz.width;
    float newHeight = ratio * sz.height;
    float newOriginX = (pcSize.width - newWidth) / 2 ;
    float newOriginY = (pcSize.height - newHeight) / 2 ;

    [imageView setFrame:CGRectMake(newOriginX, newOriginY, newWidth, newHeight)];

    [imageView setImage:[self image]];

}

  • (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
    {
    return imageView;
    }

@end

[/code]

It would be even better if I didn’t have to hard-code the (600,600) size of the ContentSizeForViewInPopover, but I guess the ImageViewcontroller is not supposed to know about the popover in which it is displaying images …

Fred


#10

For my implementation for ImageViewController I have done:

[code]- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear];

CGSize sz = [[self image] size];
[scrollView setContentSize:sz];
[scrollView setMinimumZoomScale:1.0];
[scrollView setMaximumZoomScale:5.0];

[scrollView setDelegate:self];

CGSize popoverSize = CGSizeMake(600, 600);    

[imageView setFrame:CGRectMake((popoverSize.width - sz.width) / 2, (popoverSize.height - sz.height) / 2, sz.width, sz.height)];
 
[imageView setImage:[self image]];

}

  • (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
    {
    return imageView;
    }[/code]

I don’t understand why it was recommended to look at [color=#4040FF]setContentSizeForViewInPopover[/color] as I do not believe it is required.

I could have also left out the [color=#4040FF]popoverSize[/color] variable and simply used the dimensions directly in the [color=#4040FF]setFrame:[/color] method.

Nick
http://myfirstiphoneapp.co.uk


#11

For my solution I used scrollRectToVisible:animated:

[code]- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear];

CGSize imageSize = [[self image] size];
CGSize popoverSize = self.view.bounds.size;

[scrollVIew setContentSize:imageSize];
[scrollVIew setMinimumZoomScale:1.0];
[scrollVIew setMaximumZoomScale:5.0];
[scrollVIew setDelegate:self];
CGRect center = CGRectMake((imageSize.width - popoverSize.width)/2.0,
                           (imageSize.height - popoverSize.height)/2.0, 
                           popoverSize.width, 
                           popoverSize.height);
[scrollVIew scrollRectToVisible:center animated:NO];

[imageView setFrame:CGRectMake(0, 0, imageSize.width, imageSize.height)];
[imageView setImage:[self image]];

}

  • (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
    {
    return imageView;
    }
    [/code]
    I’m not sure if it’s the best approach, but it seems to work well, and I was able to do everything inside in imageViewController.m

#12

My solution is similar to the proposed here, but I choose to use the setCenter method of the imageView instance instead of make the calc for the rect, I thing it is less messy.

Here is my ImageViewController.m

[code]-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear];

CGSize sz = [[self image] size];
[scrollView setContentSize:sz];

//allow zooming to the scrollView
[scrollView setMinimumZoomScale:1.0];
[scrollView setMaximumZoomScale:10.0];

[scrollView setDelegate:self];

[imageView setFrame:CGRectMake(0, 0, sz.width, sz.height)];

[imageView setImage:[self image]];

//Instead of changing the imageView Frame, center the imageView with setCenter
[imageView setCenter:CGPointMake([scrollView frame].size.width / 2, [scrollView frame].size.height / 2)];

}

-(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
//return the imageView for zooming
return imageView;
}[/code]

I think it is working fine, but I thank any comment with a possible mistake that I have in my solution.


#13

For my solution, it was easy enough to implement the pinch to zoom gesture. I was pleasantly surprised, as I have been doing iOS development using Flash Builder / AIR for my work and the application of pinch gesture is not difficult, it does not often work as I would like without significant effort.

Note, before I get flamed for using FB, my workplace is a non-Mac environment, so my options are limited :slight_smile:.

Like others, I was concerned with not wanting to hard code the popover size, so I attempted to utilize Joes suggestion.

ImageViewController.m

[code]- (void)viewDidLoad
{
[super viewDidLoad];

[scrollView setMinimumZoomScale:0.5];
[scrollView setMaximumZoomScale:5.0];
[scrollView setDelegate:self];

// according to docs UIPopoverController max for width is 600,
// height has no max
float w = 600;
float h;

if ( [[self image] size].width < w)
    w = [[self image] size].width

h = [[self image] size].height;

[self setContentSizeForViewInPopover:CGSizeMake(w, h)];

}

  • (void) viewWillAppear:(BOOL)animated
    {
    [super viewWillAppear];

    CGSize sz = [[self image] size];

    [scrollView setContentSize:sz];

    [imageView setFrame:CGRectMake(0, 0, sz.width, sz.height)];
    [imageView setImage:[self image]];
    }

  • (UIView *) viewForZoomingInScrollView:(UIScrollView *)scrollView
    {
    return imageView;
    }[/code]

In ItemsViewController.m, I removed the call to setPopoverContentSize: from the showImage: atIndexPath: method.

- (void) showImage:(id)sender atIndexPath:(NSIndexPath *)ip
{
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
    {
        BNRItem *i = [[[BNRItemStore sharedStore] allItems] objectAtIndex:[ip row]];
        NSString *imageKey = [i imageKey];
        
	    UIImage *img = [[BNRImageStore sharedStore] imageForKey];
        if (!img)
            return;

        CGRect rect = [[self view] convertRect:[sender bounds] fromView:sender];
        ImageViewController *ivc = [[ImageViewController alloc] init];
        [ivc setImage:img];
        
        imagePopover = [[UIPopoverController alloc] initWithContentViewController:ivc];
        [imagePopover setDelegate:self];

        // [imagePopover setPopoverContentSize:CGSizeMake([ivc contentSizeForViewInPopover].width, [ivc contentSizeForViewInPopover].height) animated:YES];

        [imagePopover presentPopoverFromRect:rect
                                      inView:[self view]
                    permittedArrowDirections:UIPopoverArrowDirectionAny
                                    animated:YES];
    }
}

This worked great to set the size of the popover so that it fit the contents but did not exceed the max value. However, when scaling the image down with pinch to zoom when the image size was smaller in width and / or height than the popover it always went to (0,0) coordinate. To fix this, I called the scrollViewDidZoom: method and did some checking to center the image when necessary.

Back in ImageViewController.m

(void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
{
	// get current image frame width and height
    float currentWidth = [imageView frame].size.width;
    float currentHeight = [imageView frame].size.height;
    
	// find width and height for popover
    float pw = [self contentSizeForViewInPopover].width;
    float ph = [self contentSizeForViewInPopover].height;
    
    float nx = 0.0;
    float ny = 0.0;
    
    if (currentWidth < pw)
    {
        nx = (pw - currentWidth) / 2;
    }
    
    if (currentHeight < ph)
    {
        ny = (ph - currentHeight) / 2;
    }

    // update the imageView frame if image width or height is smaller than the popover
    if (nx != 0.0 || ny != 0.0)
    {
        [imageView setFrame:CGRectMake(nx, ny, currentWidth, currentHeight)];
    }
}

I imagine this could have been done more elegantly, but it served my purposes.

Hope this can be of use and I am open to suggestions on cleaning this up where it makes sense.