Gold Challenge Without ScrollView


#1

I know I posted this in the other Gold Challenge thread, but I wanted to break this out into its own topic because I want to fully understand how to “drop in” gesture recognizers when I want them (dynamically), and I wanted to have a longer discussion about it.

So, the conventional wisdom is that to solve the Gold Challenge in chapter 19, you will have to use a UIScrollView and just kind of “piggyback” off of its natural scaling feature. To me, though, this seems less than ideal. It seems like the best solution would be to just drop down a UIPinchGestureRecognizer on the popover’s view and then respond to its events by usings its scale and velocity properties.

And on the surface it seems like this would be an easy thing to do. I think that if there were not this block thing involved (and I’m still not sure why we did that in the first place), the whole thing would be a lot easier. When I try it, though (and I’ve tried it many different ways), I can never get the target action to fire on the pinch gesture recognizer. At all. I just have a dummy method set up to send an NSLog message, so I’m not even trying to zoom the view yet, but I still can’t figure it out.

Has anyone else tried this method? Is it somehow completely hopeless and I just don’t know it? Is there some aspect of the block construction that is messing me up that I need to be aware of? Please help!


#2

Okay, I have literally tried everything I can possibly think of here to just get the UIPinchGestureRecognizer to fire off the action at the target. I have put the action and the target in every place I can think of (in the image view controller, in the action block, in the items view controller). The only thing in the action method is an NSLog statement at the moment, so I’m not having trouble getting the zooming functionality working. I’m just having trouble getting the gesture recognizer to actually recognize a pinch. I have never been able to get my action method to execute.

Is this just not possible with the popover?


#3

When I create a new single-view application project and add a UIPinchGestureRecognizer to the view controller’s self.view, it works just fine! I add the code to add the gesture recognizer to the end of the view controller’s viewDidLoad method. No need to create a property or anything like that. I’ve got it set to fire off a simple NSLog to let me know that it’s working, and all is well.

When I go back to Homepwner, though, I can put the exact same code in the image view controller (just like we were instructed in the challenge). I put it in the viewDidLoad method, just the same as my test project. It has the exact same action method and target (self). But it does not work in the slightest! I’ve even tried tap recognizers to see if maybe it was a problem with the implementation of “pinching” in the simulator, but even the tap recognizer does not work!

So my question to all of the experts out there: can we not write custom gesture recognizer code for a popover? It seems like the popover is the culprit.


#4

Holy cow! I found the answer! If anyone else wants to try just simply adding a pinch recognizer to the imageViewController to do the zooming, you have to make sure that the userInteractionEnabled property of the imageViewController’s view is set to YES. All objects that inherit from UIView have this property, but UIImageView sets it to NO by default. Just flip this to YES in your viewDidLoad and it’s smooth sailing!


#5

Well, it took me a while (I work on this during slow times at work - which is non-programming related, by the way), but I finally figured out how to do this without using a UIScrollView! The final piece of the puzzle for me was changing the UIViewContentMode of the image view controller to center rather than fit (“Fit” was giving me fits!). Following is the code that I used to create the same functionality using only a UIPinchGestureRecognizer.

Before switching the UIViewContentMode to center, I was trying WAY too hard to keep the positioning of the zoomed image in the center of the screen. I was having to actually crop the enlarged image to display it in the popover controller because everything was being fit into that box! The way it works now, you have to do the “fitting” yourself when the view will appear, but it makes the zooming oh so much easier.

Please forgive all of the NSLog statements. I was using them to help position and crop the image before I realized how much easier just centering everything would be.

[code]- (void)loadView
{
UIImageView *imageView = [[UIImageView alloc] init];
imageView.contentMode = UIViewContentModeCenter;
self.view = imageView;
}

  • (void)zoom:(UIGestureRecognizer *)gestureRecognizer
    {
    // Cast the gesture recognizer into a pinch gesture recognizer
    UIPinchGestureRecognizer *pinch = (UIPinchGestureRecognizer *)gestureRecognizer;
    NSLog(@“Pinch Scale: %f”, pinch.scale);

    CGRect imageFrame = self.view.frame;
    CGRect originalFrame = CGRectMake(0, 0, self.image.size.width, self.image.size.height);

    float scaleFactor = MIN(imageFrame.size.width / self.image.size.width,
    imageFrame.size.height / self.image.size.height);
    NSLog(@“ScaleFactor: %f”, scaleFactor);
    float zoomWidth = pinch.scale * originalFrame.size.width * scaleFactor;
    float zoomHeight = pinch.scale * originalFrame.size.height * scaleFactor;
    CGRect zoomedRect = CGRectMake(0, 0, zoomWidth, zoomHeight);
    NSLog(@“zoomedRect: width: %f height: %f”, zoomWidth, zoomHeight);

    NSLog(@“zoomedRect: x:%f, y:%f, width:%f, height:%f”, zoomedRect.origin.x, zoomedRect.origin.y, zoomedRect.size.width, zoomedRect.size.height);

    UIGraphicsBeginImageContextWithOptions(zoomedRect.size, NO, 0.0);

    [self.image drawInRect:zoomedRect];

    UIImage *zoomedImage = UIGraphicsGetImageFromCurrentImageContext();

    UIImageView *currentView = (UIImageView *)self.view;
    currentView.image = zoomedImage;

    UIGraphicsEndImageContext();

}

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

    // We must cast the view to UIImageView so the compiler knows it
    // is okay to send it setImage:
    UIImageView *imageView = (UIImageView *)self.view;
    float scaleFactor = MIN(self.view.frame.size.width / self.image.size.width, self.view.frame.size.height / self.image.size.height);
    CGRect popoverFrame = CGRectMake(0, 0, self.image.size.width * scaleFactor, self.image.size.height * scaleFactor);
    UIGraphicsBeginImageContextWithOptions(popoverFrame.size, NO, 0.0);
    [self.image drawInRect:popoverFrame];
    UIImage *fitImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    imageView.image = fitImage;
    }

  • (void)viewDidLoad
    {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.userInteractionEnabled = YES;
    UIPinchGestureRecognizer *pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(zoom:)];
    [self.view addGestureRecognizer:pinchRecognizer];
    }
    [/code]


#6

Hey thanks for that, I also wanted to do something without resorting to a UISCrollView and purely using a gesture recognizer, I would have never figured out the userInteractionEnabled = YES trick. I’m just no patient enough :wink:


#7

So just to get a little fancypants, I’m posting a simplification to how to zoom the UIImageView a lot easier using the transform property of UIView :

-(void) loadView
{
    UIImageView *imageView = [[UIImageView alloc] init];
//    imageView.contentMode = UIViewContentModeCenter;            // uncomment this for centering
    imageView.contentMode = UIViewContentModeScaleAspectFit;   // uncomment this for aspect fit
    self.view = imageView;
    self.view.userInteractionEnabled = YES;                                     // THANK YOU PneumaPilot !
    
    UIPinchGestureRecognizer *pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(zoomImage:)];
    [self.view addGestureRecognizer:pinchRecognizer];
}


-(void) viewWillAppear:(BOOL)animated
{
    [super viewWillAppear];
    
    UIImageView *imageView = (UIImageView *)self.view;
    imageView.image  = self.image;
}

-(void) zoomImage: (UIGestureRecognizer *)gr
{
    UIPinchGestureRecognizer *pinchGr = (UIPinchGestureRecognizer *)gr;
    CGAffineTransform transform = CGAffineTransformMakeScale(pinchGr.scale, pinchGr.scale);
    self.view.transform = transform;
}

Update :

the more I’ve looked into this, the more it seems that the correct way is to use a UIScrollView and let it handle resizing for you.
However, I wanted to mention that there’s a third way to resize the image using the gesture recognizer code which is probably even more appropriate than the two previous resizing methods in this thread. A UIImage will resize itself to the frame of it’s containing UIImageView, so instead of messing with the transform or messing with graphic context writing, you can just resize the UIImageView frame by the zoom factor.

In the end though, the resizing just isn’t as clean or work as well as the built-in functionality of a UIScrollView, which is what leads me to conclude that that is the best solution for this Gold challenge anyway.

-(void) zoomImage: (UIGestureRecognizer *)gr
{
    UIPinchGestureRecognizer *pinchGr = (UIPinchGestureRecognizer *)gr;
   
    CGRect  newSize = CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y, self.view.frame.size.width*pinchGr.scale, self.view.frame.size.height*pinchGr.scale);
    
    self.view.frame = newSize;
}
@end