Skip to content

Commit 3c44236

Browse files
Forestbrookbijingtonne0rrmatrixTheCodeTraveler
authored
Fix bug CameraView on iOS: camera preview is not always rotated correct and photos are rotated (CommunityToolkit#2312)
* Fix bug CameraView on iOS: camera preview is not always rotated correct and photos are rotated * Update src/CommunityToolkit.Maui.Camera/CameraManager.macios.cs Co-authored-by: Shaun Lawrence <shaunrlawrence@gmail.com> * Removed the null forgiving operator ! and added null checks. Removed TODO comment. * Add exception message to the OnMediaCaptureFailed call * Use `static` on iOS Devices * Assign field `previewView`, Use pattern matching on `AVMediaTypes.Video.GetConstant()` , remove iOS 11 and iOS 13 checks since `CommunityToolkit.Maui.Camera` only supports iOS 15+ * Initialize `previewView` first --------- Co-authored-by: Shaun Lawrence <shaunrlawrence@gmail.com> Co-authored-by: James Crutchley <ne0rmatrix@gmail.com> Co-authored-by: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
1 parent e97b4a6 commit 3c44236

File tree

2 files changed

+71
-35
lines changed

2 files changed

+71
-35
lines changed

samples/CommunityToolkit.Maui.Sample/CommunityToolkit.Maui.Sample.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@
101101

102102
<!-- Fixes Static Registrar causing Linker error: https://github.com/xamarin/xamarin-macios/blob/main/docs/managed-static-registrar.md -->
103103
<Target Name="SelectStaticRegistrar" AfterTargets="SelectRegistrar">
104-
<PropertyGroup Condition="'$(Registrar)' == 'managed-static' and $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))=='maccatalyst'">
104+
<PropertyGroup Condition="'$(Registrar)' == 'managed-static'">
105105
<Registrar>static</Registrar>
106106
</PropertyGroup>
107107
</Target>

src/CommunityToolkit.Maui.Camera/CameraManager.macios.cs

Lines changed: 70 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System.Buffers;
2-
using System.Diagnostics;
3-
using System.Runtime.InteropServices;
1+
using System.Diagnostics;
42
using AVFoundation;
53
using CommunityToolkit.Maui.Core.Primitives;
64
using CommunityToolkit.Maui.Extensions;
@@ -22,19 +20,26 @@ partial class CameraManager
2220

2321
AVCaptureFlashMode flashMode;
2422

23+
IDisposable? orientationDidChangeObserver;
24+
PreviewView? previewView;
25+
AVCaptureVideoOrientation videoOrientation;
26+
2527
// IN the future change the return type to be an alias
2628
public UIView CreatePlatformView()
2729
{
2830
captureSession = new AVCaptureSession
2931
{
3032
SessionPreset = AVCaptureSession.PresetPhoto
3133
};
32-
33-
var previewView = new PreviewView
34+
35+
previewView = new PreviewView
3436
{
3537
Session = captureSession
3638
};
3739

40+
orientationDidChangeObserver = UIDevice.Notifications.ObserveOrientationDidChange((_, _) => UpdateVideoOrientation());
41+
UpdateVideoOrientation();
42+
3843
return previewView;
3944
}
4045

@@ -194,41 +199,51 @@ protected virtual async partial ValueTask PlatformTakePicture(CancellationToken
194199
var capturePhotoSettings = AVCapturePhotoSettings.FromFormat(codecSettings);
195200
capturePhotoSettings.FlashMode = photoOutput.SupportedFlashModes.Contains(flashMode) ? flashMode : photoOutput.SupportedFlashModes.First();
196201

202+
if (AVMediaTypes.Video.GetConstant() is NSString avMediaTypeVideo)
203+
{
204+
var photoOutputConnection = photoOutput.ConnectionFromMediaType(avMediaTypeVideo);
205+
if (photoOutputConnection is not null)
206+
{
207+
photoOutputConnection.VideoOrientation = videoOrientation;
208+
}
209+
}
210+
197211
var wrapper = new AVCapturePhotoCaptureDelegateWrapper();
198212

199213
photoOutput.CapturePhoto(capturePhotoSettings, wrapper);
200214

201215
var result = await wrapper.Task.WaitAsync(token);
202-
var data = result.Photo.FileDataRepresentation;
203-
204216
if (result.Error is not null)
205217
{
206-
cameraView.OnMediaCapturedFailed(result.Error.LocalizedFailureReason);
207-
return;
208-
}
218+
var failureReason = result.Error.LocalizedDescription;
219+
if (!string.IsNullOrEmpty(result.Error.LocalizedFailureReason))
220+
{
221+
failureReason = $"{failureReason} - {result.Error.LocalizedFailureReason}";
222+
}
209223

210-
if (data is null)
211-
{
212-
cameraView.OnMediaCapturedFailed("Unable to retrieve the file data representation from the captured result.");
224+
cameraView.OnMediaCapturedFailed(failureReason);
213225
return;
214226
}
215227

216-
var dataBytes = ArrayPool<byte>.Shared.Rent((int)data.Length);
217-
228+
Stream? imageData;
218229
try
219230
{
220-
Marshal.Copy(data.Bytes, dataBytes, 0, (int)data.Length);
221-
222-
cameraView.OnMediaCaptured(new MemoryStream(dataBytes));
231+
imageData = result.Photo.FileDataRepresentation?.AsStream();
232+
}
233+
catch (Exception e)
234+
{
235+
// possible exception: ObjCException NSInvalidArgumentException NSAllocateMemoryPages(...) failed in AVCapturePhoto.get_FileDataRepresentation()
236+
cameraView.OnMediaCapturedFailed($"Unable to retrieve the file data representation from the captured result: {e.Message}");
237+
return;
223238
}
224-
catch (Exception ex)
239+
240+
if (imageData is null)
225241
{
226-
cameraView.OnMediaCapturedFailed(ex.Message);
227-
throw;
242+
cameraView.OnMediaCapturedFailed("Unable to retrieve the file data representation from the captured result.");
228243
}
229-
finally
244+
else
230245
{
231-
ArrayPool<byte>.Shared.Return(dataBytes);
246+
cameraView.OnMediaCaptured(imageData);
232247
}
233248
}
234249

@@ -243,11 +258,37 @@ protected virtual void Dispose(bool disposing)
243258
captureInput?.Dispose();
244259
captureInput = null;
245260

261+
orientationDidChangeObserver?.Dispose();
262+
orientationDidChangeObserver = null;
263+
246264
photoOutput?.Dispose();
247265
photoOutput = null;
248266
}
249267
}
250268

269+
static AVCaptureVideoOrientation GetVideoOrientation()
270+
{
271+
IEnumerable<UIScene> scenes = UIApplication.SharedApplication.ConnectedScenes;
272+
var interfaceOrientation = scenes.FirstOrDefault() is UIWindowScene windowScene
273+
? windowScene.InterfaceOrientation
274+
: UIApplication.SharedApplication.StatusBarOrientation;
275+
276+
return interfaceOrientation switch
277+
{
278+
UIInterfaceOrientation.Portrait => AVCaptureVideoOrientation.Portrait,
279+
UIInterfaceOrientation.PortraitUpsideDown => AVCaptureVideoOrientation.PortraitUpsideDown,
280+
UIInterfaceOrientation.LandscapeRight => AVCaptureVideoOrientation.LandscapeRight,
281+
UIInterfaceOrientation.LandscapeLeft => AVCaptureVideoOrientation.LandscapeLeft,
282+
_ => AVCaptureVideoOrientation.Portrait
283+
};
284+
}
285+
286+
void UpdateVideoOrientation()
287+
{
288+
videoOrientation = GetVideoOrientation();
289+
previewView?.UpdatePreviewVideoOrientation(videoOrientation);
290+
}
291+
251292
sealed class AVCapturePhotoCaptureDelegateWrapper : AVCapturePhotoCaptureDelegate
252293
{
253294
readonly TaskCompletionSource<CapturePhotoResult> taskCompletionSource = new();
@@ -299,20 +340,15 @@ public AVCaptureSession? Session
299340
public override void LayoutSubviews()
300341
{
301342
base.LayoutSubviews();
343+
UpdatePreviewVideoOrientation(GetVideoOrientation());
344+
}
302345

303-
if (PreviewLayer.Connection is null)
346+
public void UpdatePreviewVideoOrientation(AVCaptureVideoOrientation videoOrientation)
347+
{
348+
if (PreviewLayer.Connection is not null)
304349
{
305-
return;
350+
PreviewLayer.Connection.VideoOrientation = videoOrientation;
306351
}
307-
308-
PreviewLayer.Connection.VideoOrientation = UIDevice.CurrentDevice.Orientation switch
309-
{
310-
UIDeviceOrientation.Portrait => AVCaptureVideoOrientation.Portrait,
311-
UIDeviceOrientation.PortraitUpsideDown => AVCaptureVideoOrientation.PortraitUpsideDown,
312-
UIDeviceOrientation.LandscapeLeft => AVCaptureVideoOrientation.LandscapeRight,
313-
UIDeviceOrientation.LandscapeRight => AVCaptureVideoOrientation.LandscapeLeft,
314-
_ => PreviewLayer.Connection.VideoOrientation
315-
};
316352
}
317353
}
318354
}

0 commit comments

Comments
 (0)