Search code examples
iosxamarinxamarin.iosavcam

iOS VideoPreview Landscape is upside down


For some reason when I orient the phone to landscape mode the camera preview is upside down (flipped horizontally). I'm using the sample project that Xamarin ported; AVCamBarcode.

I ended up coming up with a half complete fix by using GeometryFlipped in landscape orientation. However, now the drawn barcodes are not drawn in the correct locations in landscape mode.

  1. Is this a device/os issue or is the sample incomplete?
  2. What is the recommended method to correct this?

    public override void ViewWillTransitionToSize (CGSize toSize, IUIViewControllerTransitionCoordinator coordinator)
    {
        base.ViewWillTransitionToSize (toSize, coordinator);
    
        var videoPreviewLayerConnection = PreviewView.VideoPreviewLayer.Connection;
        if (videoPreviewLayerConnection != null) {
            var deviceOrientation = UIDevice.CurrentDevice.Orientation;
            if (!deviceOrientation.IsPortrait () && !deviceOrientation.IsLandscape ())
                return;
    
            var newVideoOrientation = VideoOrientationFor (deviceOrientation);
            var oldSize = View.Frame.Size;
            var oldVideoOrientation = videoPreviewLayerConnection.VideoOrientation;
            videoPreviewLayerConnection.VideoOrientation = newVideoOrientation;
    
            //THIS IS THE HALF FIX I CAME UP WITH
            if (deviceOrientation.IsLandscape())
                videoPreviewLayerConnection.VideoPreviewLayer.GeometryFlipped = true;
            else
                videoPreviewLayerConnection.VideoPreviewLayer.GeometryFlipped = false;
            //END FIX
    
            // When we transition to the new size, we need to adjust the region
            // of interest's origin and size so that it stays anchored relative
            // to the camera.
            coordinator.AnimateAlongsideTransition (context => {
                var oldRegion = PreviewView.RegionOfInterest;
                var newRegion = new CGRect ();
    
                if (oldVideoOrientation == LandscapeRight && newVideoOrientation == LandscapeLeft) {
                    newRegion = oldRegion.WithX (oldSize.Width - oldRegion.X - oldRegion.Width);
                } else if (oldVideoOrientation == LandscapeRight && newVideoOrientation == Portrait) {
                    newRegion.X = toSize.Width - oldRegion.Y - oldRegion.Height;
                    newRegion.Y = oldRegion.X;
                    newRegion.Width = oldRegion.Height;
                    newRegion.Height = oldRegion.Width;
                } else if (oldVideoOrientation == LandscapeLeft && newVideoOrientation == LandscapeRight) {
                    newRegion = oldRegion.WithX (oldSize.Width - oldRegion.X - oldRegion.Width);
                } else if (oldVideoOrientation == LandscapeLeft && newVideoOrientation == Portrait) {
                    newRegion.X = oldRegion.Y;
                    newRegion.Y = oldSize.Width - oldRegion.X - oldRegion.Width;
                    newRegion.Width = oldRegion.Height;
                    newRegion.Height = oldRegion.Width;
                } else if (oldVideoOrientation == Portrait && newVideoOrientation == LandscapeRight) {
                    newRegion.X = oldRegion.Y;
                    newRegion.Y = toSize.Height - oldRegion.X - oldRegion.Width;
                    newRegion.Width = oldRegion.Height;
                    newRegion.Height = oldRegion.Width;
                } else if (oldVideoOrientation == Portrait && newVideoOrientation == LandscapeLeft) {
                    newRegion.X = oldSize.Height - oldRegion.Y - oldRegion.Height;
                    newRegion.Y = oldRegion.X;
                    newRegion.Width = oldRegion.Height;
                    newRegion.Height = oldRegion.Width;
                }
    
                PreviewView.SetRegionOfInterestWithProposedRegionOfInterest (newRegion);
            }, context => {
                sessionQueue.DispatchAsync (() => {
                    metadataOutput.RectOfInterest = PreviewView.VideoPreviewLayer.MapToLayerCoordinates (PreviewView.RegionOfInterest);
                });
                // Remove the old metadata object overlays.
                RemoveMetadataObjectOverlayLayers ();
            });
        }
    }
    

Edit

AVCaptureVideoOrientation VideoOrientationFor (UIDeviceOrientation deviceOrientation)
{
    switch (deviceOrientation) {
    case UIDeviceOrientation.Portrait:
        return Portrait;
    case UIDeviceOrientation.PortraitUpsideDown:
        return PortraitUpsideDown;
    case UIDeviceOrientation.LandscapeLeft:
        return LandscapeLeft;
    case UIDeviceOrientation.LandscapeRight:
        return LandscapeRight;
    default:
        throw new InvalidProgramException ();
    }
}

Solution

  • Device and video orientation have different meanings for landscape.

    The docs say (for video):

    case landscapeLeft

    Indicates that video should be oriented horizontally, top on the right.

    case landscapeRight

    Indicates that video should be oriented horizontally, top on the left.

    but device orientation says:

    case landscapeLeft

    The device is in landscape mode, with the device held upright and the home button on the right side.

    case landscapeRight

    The device is in landscape mode, with the device held upright and the home button on the left side.

    So, you need to change to this:

    case UIDeviceOrientation.LandscapeLeft:
        return LandscapeRight;
    case UIDeviceOrientation.LandscapeRight:
        return LandscapeLeft;