Question

I am using an UIImagePickerView to grab photos from the camera or camera roll. When the user 'picks' an image, I'm inserting the image onto an UIImageView, which is nested in a UIScrollView to allow pinch/pan. I have an overlay above the image view which represents the area to which the image will be cropped (just like when UIImagePickerView's .allowEditing property is YES).

The Apple-provided "allowEditing" capability also has the same problem I'm seeing with my code (which I why I tried to write it myself in the first place, and I need custom shapes in the overlay). The problem is that I can't seem to find a good way to allow the user to pan around over ALL the image. There are always portions of the image which can't be placed in the crop window. It's always content around the edges (maybe the outside 10%) of the image, which cannot be panned into the crop window.

unusable crop area

In the above photo, the brown area at the top and bottom are the scroll view's background color. The image view is sized to the image.

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {

    UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];

    // Calculate what height/width we'll need for our UIImageView.
    CGSize screenSize = [UIScreen mainScreen].bounds.size;
    float width = 0.0f, height = 0.0f;
    if (image.size.width > image.size.height) {
        width = screenSize.width;
        height =  image.size.height / (image.size.width/screenSize.width);
    } else {
        height = screenSize.height;
        width = image.size.width / (image.size.height/screenSize.height);
    }

    if (width > screenSize.width) {
        height /= (width/screenSize.width);
        width = screenSize.width;
    }
    if (height > screenSize.height) {
        width /= (height/screenSize.height);
        height = screenSize.height;
    }

    // Update the image view to the size of the image and center it.
    // Image view is a subview of the scroll view.
    imageView.frame = CGRectMake((screenSize.width - width) / 2, (screenSize.height - height) / 2, width, height);
    imageView.image = image;
    // Setup our scrollview so we can scroll and pinch zoom the image!
    imageScrollView.contentSize = CGSizeMake(screenSize.width, screenSize.height);

    // Close the picker.
    [[picker presentingViewController] dismissViewControllerAnimated:YES completion:NULL];
}

I've considered monitoring scroll position and zoom level of the scroll view and disallow a side of the image to pass into the crop "sweet spot" of the image. This seems like over-engineering, however.

Does anyone know of a way to accomplish this?

Was it helpful?

Solution

I'm a moron. What a difference a good night's sleep can make ;-) Hopefully, it will help someone in the future.

Setting the correct scroll view contentSize and contentInset did the trick. The working code is below.

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {

    UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];

    // Change the frame of the image view so that it fits the image!
    CGSize screenSize = [UIScreen mainScreen].bounds.size;
    float width = 0.0f, height = 0.0f;
    if (image.size.width > image.size.height) {
        width = screenSize.width;
        height = image.size.height / (image.size.width/screenSize.width);
    } else {
        height = screenSize.height;
        width = image.size.width / (image.size.height/screenSize.height);
    }

    // Make sure the new height and width aren't bigger than the screen
    if (width > screenSize.width) {
        height /= (width/screenSize.width);
        width = screenSize.width;
    }
    if (height > screenSize.height) {
        width /= (height/screenSize.height);
        height = screenSize.height;
    }

    CGRect overlayRect = cropOverlay.windowRect;

    imageView.frame = CGRectMake((screenSize.width - width) / 2, (screenSize.height - height) / 2, width, height);
    imageView.image = image;

    // Setup our scrollview so we can scroll and pinch zoom the image!
    imageScrollView.contentSize = imageView.frame.size;
    imageScrollView.contentInset = UIEdgeInsetsMake(overlayRect.origin.y - imageView.frame.origin.y,
                                                    overlayRect.origin.x,
                                                    overlayRect.origin.y + imageView.frame.origin.y,
                                                    screenSize.width - (overlayRect.origin.x + overlayRect.size.width));

    // Dismiss the camera's VC
    [[picker presentingViewController] dismissViewControllerAnimated:YES completion:NULL];
}

The scrollview and image view are set up like this:

imageScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
imageScrollView.showsHorizontalScrollIndicator = NO;
imageScrollView.showsVerticalScrollIndicator = NO;
imageScrollView.backgroundColor = [UIColor blackColor];
imageScrollView.userInteractionEnabled = YES;
imageScrollView.delegate = self;
imageScrollView.minimumZoomScale = MINIMUM_SCALE;
imageScrollView.maximumZoomScale = MAXIMUM_SCALE;
[self.view addSubview:imageScrollView];

imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.backgroundColor = [UIColor grayColor]; // I had this set to gray so I could see if/when it didn't align properly in the scroll view.  You'll likely want to change it to black
[imageScrollView addSubview:imageView];

Edit 3-21-14 Newer, fancier, better implementation of the method that calculates where to place the image in the screen and scrollview. So what's better? This new implementation will check for any image that is being set into the scrollview which is SMALLER in width or height, and adjust the frame of the image view such that it expands to be at least as wide or tall as the overlay rect, so you don't ever have to worry about your user selecting an image that isn't optimal for your overlay. Yay!

- (void)imagePickerController:(UIImagePickerController *)pickerUsed didFinishPickingMediaWithInfo:(NSDictionary *)info {

    UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];

    // Change the frame of the image view so that it fits the image!
    CGSize screenSize = [UIScreen mainScreen].bounds.size;
    float width = 0.0f, height = 0.0f;
    if (image.size.width > image.size.height) {
        width = screenSize.width;
        height = image.size.height / (image.size.width/screenSize.width);
    } else {
        height = screenSize.height;
        width = image.size.width / (image.size.height/screenSize.height);
   }

   CGRect overlayRect = cropOverlay.windowRect;

    // We should check the the width and height are at least as big as our overlay window
    if (width < overlayRect.size.width) {
        float ratio = overlayRect.size.width / width;
        width *= ratio;
        height *= ratio;
    }
    if (height < overlayRect.size.height) {
        float ratio = overlayRect.size.height / height;
        height *= ratio;
        width *= ratio;
    }

    CGRect imageViewFrame = CGRectMake((screenSize.width - width) / 2, (screenSize.height - height) / 2, width, height);
    imageView.frame = imageViewFrame;
    imageView.image = image;

    // Setup our scrollview so we can scroll and pinch zoom the image!
    imageScrollView.contentSize = imageView.frame.size;
    imageScrollView.contentInset = UIEdgeInsetsMake(overlayRect.origin.y - imageView.frame.origin.y,
                                                    (imageViewFrame.origin.x * -1) + overlayRect.origin.x,
                                                    overlayRect.origin.y + imageView.frame.origin.y,
                                                    imageViewFrame.origin.x + (screenSize.width - (overlayRect.origin.x + overlayRect.size.width)));

    // Calculate the REAL minimum zoom scale!
    float minZoomScale = 1 - MIN(fabsf(fabsf(imageView.frame.size.width) -  fabsf(overlayRect.size.width)) /  imageView.frame.size.width,
                           fabsf(fabsf(imageView.frame.size.height) - fabsf(overlayRect.size.height)) / imageView.frame.size.height);
    imageScrollView.minimumZoomScale = minZoomScale;

    // Dismiss the camera's VC
    [[picker presentingViewController] dismissViewControllerAnimated:YES completion:NULL];
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top