47

I have a UIImagePicker that works perfect for a type of UIImagePickerControllerSourceTypePhotoLibrary, but when I use UIImagePickerControllerSourceTypeCamera, the editing box cannot move from the center of the image. So if the image is say taller than it is wide, the user cannot move the editing box to the top square of the image.

Anyone know why this would be the case? It only happens when the source is from the camera, not the library.

Edit: Some CODE!!!

if (actionSheet.tag == 2) {
    if (buttonIndex == 0) { // Camera
        // Check for camera
        if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera] == YES) {
            // Create image picker controller
            UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];

            // Set source to the camera
            imagePicker.sourceType =  UIImagePickerControllerSourceTypeCamera;
            imagePicker.allowsEditing = YES;

            // Delegate is self
            imagePicker.delegate = self;

            // Show image picker
            [self presentViewController:imagePicker 
                               animated:YES 
                             completion:^(void) {
                             }];
        }
    }
    else if (buttonIndex == 1) { // Photo Library
        if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary] == YES) {
            // Create image picker controller
            UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];

            // Set source to the camera
            imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
            imagePicker.allowsEditing = YES;

            // Delegate is self
            imagePicker.delegate = self;

            // Show image picker
            [self presentViewController:imagePicker 
                               animated:YES 


                          completion:^(void) {
                                 }];
            }
}

So as you can see, I display them the exact same, but the camera edit acts differently than the photo library edit.

Eric
  • 5,671
  • 5
  • 31
  • 42

8 Answers8

54

Looks like this behavior is just a bug in iOS 6... Basically you cannot move the editing box, it always bounces back to the middle unless you zoom in a bit. Hopefully they fix that soon.

Eric
  • 5,671
  • 5
  • 31
  • 42
4

Thanks yycking. This extension works. Except I added the method call inside viewDidLayoutSubviews so that I don't have to call it every time I want to open image picker.

Here's the full extenstion

extension UIImagePickerController {
    open override var childForStatusBarHidden: UIViewController? {
        return nil
    }

    open override var prefersStatusBarHidden: Bool {
        return true
    }
    
    open override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        fixCannotMoveEditingBox()
    }
    
    func fixCannotMoveEditingBox() {
            if let cropView = cropView,
               let scrollView = scrollView,
               scrollView.contentOffset.y == 0 {
                
                var top: CGFloat = 0.0
                if #available(iOS 11.0, *) {
                    top = cropView.frame.minY + self.view.safeAreaInsets.top
                } else {
                    // Fallback on earlier versions
                    top = cropView.frame.minY
                }
                let bottom = scrollView.frame.height - cropView.frame.height - top
                scrollView.contentInset = UIEdgeInsets(top: top, left: 0, bottom: bottom, right: 0)
                
                var offset: CGFloat = 0
                if scrollView.contentSize.height > scrollView.contentSize.width {
                    offset = 0.5 * (scrollView.contentSize.height - scrollView.contentSize.width)
                }
                scrollView.contentOffset = CGPoint(x: 0, y: -top + offset)
            }
            
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
                self?.fixCannotMoveEditingBox()
            }
        }
        
        var cropView: UIView? {
            return findCropView(from: self.view)
        }
        
        var scrollView: UIScrollView? {
            return findScrollView(from: self.view)
        }
        
        func findCropView(from view: UIView) -> UIView? {
            let width = UIScreen.main.bounds.width
            let size = view.bounds.size
            if width == size.height, width == size.height {
                return view
            }
            for view in view.subviews {
                if let cropView = findCropView(from: view) {
                    return cropView
                }
            }
            return nil
        }
        
        func findScrollView(from view: UIView) -> UIScrollView? {
            if let scrollView = view as? UIScrollView {
                return scrollView
            }
            for view in view.subviews {
                if let scrollView = findScrollView(from: view) {
                    return scrollView
                }
            }
            return nil
        }
}
Raj D
  • 205
  • 3
  • 16
2

Reset contentInset of scrollview:

extension UIImagePickerController {
    func fixCannotMoveEditingBox() {
        if let cropView = cropView,
           let scrollView = scrollView,
           scrollView.contentOffset.y == 0 {
            
            let top = cropView.frame.minY + self.view.safeAreaInsets.top
            let bottom = scrollView.frame.height - cropView.frame.height - top
            scrollView.contentInset = UIEdgeInsets(top: top, left: 0, bottom: bottom, right: 0)
            
            var offset: CGFloat = 0
            if scrollView.contentSize.height > scrollView.contentSize.width {
                offset = 0.5 * (scrollView.contentSize.height - scrollView.contentSize.width)
            }
            scrollView.contentOffset = CGPoint(x: 0, y: -top + offset)
        }
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
            self?.fixCannotMoveEditingBox()
        }
    }
    
    var cropView: UIView? {
        return findCropView(from: self.view)
    }
    
    var scrollView: UIScrollView? {
        return findScrollView(from: self.view)
    }
    
    func findCropView(from view: UIView) -> UIView? {
        let width = UIScreen.main.bounds.width
        let size = view.bounds.size
        if width == size.height, width == size.height {
            return view
        }
        for view in view.subviews {
            if let cropView = findCropView(from: view) {
                return cropView
            }
        }
        return nil
    }
    
    func findScrollView(from view: UIView) -> UIScrollView? {
        if let scrollView = view as? UIScrollView {
            return scrollView
        }
        for view in view.subviews {
            if let scrollView = findScrollView(from: view) {
                return scrollView
            }
        }
        return nil
    }
}

then call it

imagePickercontroller.fixCannotMoveEditingBox()
vvvvv
  • 25,404
  • 19
  • 49
  • 81
yycking
  • 1,017
  • 1
  • 9
  • 14
2

Here is an extension I end up using that works fine on both notch and non-notch devices. And works perfectly on iOS 15!

extension UIImagePickerController {
    open override var childForStatusBarHidden: UIViewController? {
        return nil
    }

    open override var prefersStatusBarHidden: Bool {
        return true
    }
    
    open override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        fixCannotMoveEditingBox()
    }
    
    private func fixCannotMoveEditingBox() {
        if let cropView = cropView, let scrollView = scrollView, scrollView.contentOffset.y == 0 {
            let top: CGFloat = cropView.frame.minY + self.view.frame.minY
            let bottom = scrollView.frame.height - cropView.frame.height - top
            scrollView.contentInset = UIEdgeInsets(top: top, left: 0, bottom: bottom, right: 0)
            
            var offset: CGFloat = 0
            if scrollView.contentSize.height > scrollView.contentSize.width {
                offset = 0.5 * (scrollView.contentSize.height - scrollView.contentSize.width)
            }
            scrollView.contentOffset = CGPoint(x: 0, y: -top + offset)
        }
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
            self?.fixCannotMoveEditingBox()
        }
    }
    
    private var cropView: UIView? {
        return findCropView(from: self.view)
    }
    
    private var scrollView: UIScrollView? {
        return findScrollView(from: self.view)
    }
    
    private func findCropView(from view: UIView) -> UIView? {
        let width = UIScreen.main.bounds.width
        let size = view.bounds.size
        if width == size.height, width == size.height {
            return view
        }
        for view in view.subviews {
            if let cropView = findCropView(from: view) {
                return cropView
            }
        }
        return nil
    }
    
    private func findScrollView(from view: UIView) -> UIScrollView? {
        if let scrollView = view as? UIScrollView {
            return scrollView
        }
        for view in view.subviews {
            if let scrollView = findScrollView(from: view) {
                return scrollView
            }
        }
        return nil
    }
}

p.s.: the only changes from other similar answers are:

  • dropping support for an old iOS version (like iOS 11);
  • a bit different way of calculating contentInset top property;
Sergio
  • 385
  • 4
  • 11
0

This is the default behavior the Image Picker Controller, you can not change it. The only other option is to create your own cropping utility. Check out the link below for an example:

https://github.com/ardalahmet/SSPhotoCropperViewController

Vikings
  • 2,527
  • 32
  • 45
  • 3
    But it works when you pick from an album.. Doesn't make sense. Why would you be able to move it more when its from an album rather than camera – Eric Sep 28 '12 at 04:24
  • @Eric the editing box does not move at all, it is fixed in the middle of the screen, and you are able to scale the picture to fit in the box. Take a screenshot, because your code is right – Vikings Sep 28 '12 at 21:45
  • Ill add a screen shot as soon as I can but what I mean is the static box stays still (which is correct), but the image underneath that moves cannot be moved so the edit box is in the top area of the image. If you scroll to that area the image 'bounces back' to the center of the image... – Eric Oct 01 '12 at 17:01
  • 1
    this isnt the default behaviour, i believe it is a bug in ios6. On previous ios versions it works fine, but on ios6 the image bounces back when you try and move it. However it does work slightly if you zoom in a bit. – Robert Oct 24 '12 at 22:44
  • @Robert Oh ok.. That's what I figured! I guess I will just let it be until they update it.. – Eric Oct 31 '12 at 20:39
0

I know, this is not a good solution, but it works.

I tested on iOS8+iPhone5, iOS9+iPhone6sPlus, iOS10+iPhone6, iOS10+iPhone6sPlus.

CAUTION: PLImageScrollView and PLCropOverlayCropView are UNDOCUMENTED classes.

- (void)showImagePickerControllerWithSourceType:(UIImagePickerControllerSourceType)sourceType {
    UIImagePickerController *imagePickerController = [UIImagePickerController new];
    imagePickerController.sourceType = sourceType;
    imagePickerController.mediaTypes = @[(NSString *)kUTTypeImage];
    imagePickerController.allowsEditing = YES;
    imagePickerController.delegate = self;
    [self presentViewController:imagePickerController animated:YES completion:^{
        [self fxxxImagePickerController:imagePickerController];
    }];
}

- (void)fxxxImagePickerController:(UIImagePickerController *)imagePickerController {
    if (!imagePickerController
        || !imagePickerController.allowsEditing
        || imagePickerController.sourceType != UIImagePickerControllerSourceTypeCamera) {
        return;
    }

    // !!!: UNDOCUMENTED CLASS
    Class ScrollViewClass = NSClassFromString(@"PLImageScrollView");
    Class CropViewClass = NSClassFromString(@"PLCropOverlayCropView");

    [imagePickerController.view eachSubview:^BOOL(UIView *subview, NSInteger depth) {
        if ([subview isKindOfClass:CropViewClass]) {
            // 0. crop rect position
            subview.frame = subview.superview.bounds;
        }
        else if ([subview isKindOfClass:[UIScrollView class]]
            && [subview isKindOfClass:ScrollViewClass]) {
            BOOL isNewImageScrollView = !self->_imageScrollView;
            self->_imageScrollView = (UIScrollView *)subview;
            // 1. enable scrolling
            CGSize size = self->_imageScrollView.frame.size;
            CGFloat inset = ABS(size.width - size.height) / 2;
            self->_imageScrollView.contentInset = UIEdgeInsetsMake(inset, 0, inset, 0);
            // 2. centering image by default
            if (isNewImageScrollView) {
                CGSize contentSize = self->_imageScrollView.contentSize;
                if (contentSize.height > contentSize.width) {
                    CGFloat offset = round((contentSize.height - contentSize.width) / 2 - inset);
                    self->_imageScrollView.contentOffset = CGPointMake(self->_imageScrollView.contentOffset.x, offset);
                }
            }
        }
        return YES;
    }];

    // prevent re-layout, maybe not necessary
    @weakify(self, imagePickerController);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        @strongify(self, imagePickerController);
        [self fxxxImagePickerController:imagePickerController];
    });
}

EDIT: The eachSubview: method traverses all the subviews tree.

Míng
  • 2,500
  • 6
  • 32
  • 48
0

If you have set "View controller-based status bar appearance" to NO in info.plist and set status bar appearance as light using

 UIApplication.shared.statusBarStyle = .lightContent

or using any other method , Then simply set the style as .default before presenting the image picker. for Eg:

imagePicker.allowsEditing = true
imagePicker.sourceType = .photoLibrary
UIApplication.shared.statusBarStyle = .default
present(imagePicker, animated: true, completion: nil)

Change the source type according to your need either as photoLibrary or camera and in completion block of your didFinishPickingMediaWithInfo add the following to completion block.

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
    //let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage

    var pickedImage : UIImage?
    if let img = info[UIImagePickerControllerEditedImage] as? UIImage
    {
        pickedImage = img

    }
    else if let img = info[UIImagePickerControllerOriginalImage] as? UIImage
    {
        pickedImage = img
    }
    dismiss(animated: true, completion: {
        UIApplication.shared.statusBarStyle         = .lightContent
    })}

Apparently this is a workaround for the same.Hope this helps.

SwiftNinja95
  • 157
  • 2
  • 16
-1

A workaround that solved it is to add an entry in info.plist with "View controller-based status bar appearance" set to NO

Josep Alsina
  • 2,762
  • 1
  • 16
  • 12