DEV Community

Edward Miller
Edward Miller

Posted on • Updated on

.NET MAUI iOS Camera Photos Rotated bug fix

At the time of writing this article, there is a bug in MAUI that has existed since Xamarin.Forms where photos taken from iOS do not respect the device orientation, or record that information in the EXIF data, and thus they can end up incorrectly rotated.

This one was one of the most annoying bugs in MAUI, and I solved it with this workaround taken from IeuanWalker on this thread.

I adapted the code to more precisely conform to the existing MediaPicker conventions, and am using nullable types.

Just put this in your Platforms/iOS folder:

namespace YourAppName.Platforms.iOS;

using System;
using System.Threading.Tasks;
using Foundation;
using Microsoft.Maui.Graphics.Platform;
using UIKit;

public static class PhotoCaptureUtil
{
    public static async Task<FileResult?> CapturePhotoAsync(MediaPickerOptions? options = null)
    {
        options ??= new MediaPickerOptions { Title = "Take a photo" };

        return await MainThread.InvokeOnMainThreadAsync(async () => await InternalCapturePhotoAsync(options));
    }

    private static async Task<FileResult?> InternalCapturePhotoAsync(MediaPickerOptions options)
    {
        var taskCompletionSource = new TaskCompletionSource<FileResult?>();

        // Create an image picker object
        var imagePicker = new UIImagePickerController
        {
            SourceType = UIImagePickerControllerSourceType.Camera,
            MediaTypes = UIImagePickerController.AvailableMediaTypes(UIImagePickerControllerSourceType.PhotoLibrary),
        };

        if (string.IsNullOrEmpty(options.Title))
        {
            imagePicker.Title = options.Title;
        }

        var vc = Platform.GetCurrentUIViewController() ?? throw new InvalidOperationException($"Cannot retrieve {nameof(UIViewController)}");

        imagePicker.AllowsEditing = false;
        imagePicker.FinishedPickingMedia += async (sender, e) =>
        {
            var jpegFilename = Path.Combine(FileSystem.CacheDirectory, $"{Guid.NewGuid()}.jpg");
            var uiImage = e.Info[UIImagePickerController.OriginalImage] as UIImage;
            var normalizedImage = uiImage.NormalizeOrientation();
            var normalizedData = normalizedImage.AsJPEG();

            await vc.DismissViewControllerAsync(true);

            if (normalizedData.Save(jpegFilename, false, out var error))
            {
                _ = taskCompletionSource.TrySetResult(new FileResult(jpegFilename));
            }
            else
            {
                _ = taskCompletionSource.TrySetException(new IOException($"Error saving the image: {error}"));
            }

            imagePicker?.Dispose();
            imagePicker = null;
        };

        imagePicker.Canceled += async (sender, e) =>
        {
            await vc.DismissViewControllerAsync(true);
            _ = taskCompletionSource.TrySetResult(null);
            imagePicker?.Dispose();
            imagePicker = null;
        };

        await vc.PresentViewControllerAsync(imagePicker, true);

        return await taskCompletionSource.Task;
    }

    private class CameraDelegate : UIImagePickerControllerDelegate
    {
        public override void FinishedPickingMedia(UIImagePickerController picker, NSDictionary info) => picker.DismissViewController(false, null);
    }
}
Enter fullscreen mode Exit fullscreen mode

and then call it like so:

    if (!MediaPicker.IsCaptureSupported)
    {
        await Shell.Current.DisplayAlertAsync("Camera permissions are not enabled on this platform.");
        return;
    }

#if IOS
    var fileResult = await PhotoCaptureUtil.CapturePhotoAsync();
#else
    var fileResult = await MediaPicker.CapturePhotoAsync();
#endif
Enter fullscreen mode Exit fullscreen mode

Top comments (0)