はじめに
こんにちは、@hiro128_777です。
前回は、Swift のコードPhotoCaptureDelegate.swift
のコールバックメソッドの定義部分を移植しました。
今回は、前回の続きです。PhotoCaptureDelegate.swift
のコールバックメソッドの実装を Xamarin.iOS へ移植していきましょう。
PhotoCaptureDelegate.swift コールバックメソッドの実装の Xamarin.iOS への移植
WillBeginCapture
では、早速、1つ目のコールバックの Swift のコードを見ていきましょう。
Swift
func photoOutput(_ output: AVCapturePhotoOutput, willBeginCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings) { if resolvedSettings.livePhotoMovieDimensions.width > 0 && resolvedSettings.livePhotoMovieDimensions.height > 0 { livePhotoCaptureHandler(true) } }
その1でご説明したように、livePhotoCaptureHandler
に対応するC#のデリゲートはcapturingLivePhoto
ですので、そこだけ気をつけて書き換えれば、後はベタに移植するだけです。
public override void WillBeginCapture (AVCapturePhotoOutput captureOutput, AVCaptureResolvedPhotoSettings resolvedSettings) { if (resolvedSettings.LivePhotoMovieDimensions.Width > 0 && resolvedSettings.LivePhotoMovieDimensions.Height > 0) capturingLivePhoto (true); }
WillCapturePhoto
2つ目です。1つ目同様にC#のデリゲートの対応だけ確認すれば後は簡単です。
Swift
func photoOutput(_ output: AVCapturePhotoOutput, willCapturePhotoFor resolvedSettings: AVCaptureResolvedPhotoSettings) { willCapturePhotoAnimation() }
public override void WillCapturePhoto (AVCapturePhotoOutput captureOutput, AVCaptureResolvedPhotoSettings resolvedSettings) { willCapturePhotoAnimation (); }
DidFinishProcessingPhoto
3つ目ですが、これは Swift と Xamarin.iOS で既にAPIが違っています。Appleのリファレンスを確認したところ、Swift のサンプルは、iOS11 対応で、 Xamarin.iOS は iOS10 対応のためです。
調べたところjpegPhotoDataRepresentation
は iOS11で Deprecated になっていました。
Swift
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { if let error = error { print("Error capturing photo: \(error)") } else { photoData = photo.fileDataRepresentation() } }
このまま移植も可能ですが、わかりにくいので iOS10 対応のサンプルを探したところ以下のようになっていました。これで見比べるとAPIがそろっていますね。
Swift iOS10対応
func capture(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhotoSampleBuffer photoSampleBuffer: CMSampleBuffer?, previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?) { if let photoSampleBuffer = photoSampleBuffer { photoData = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: photoSampleBuffer, previewPhotoSampleBuffer: previewPhotoSampleBuffer) } else { print("Error capturing photo: \(error)") return } }
わかりにくいのは、前にも出てきたif let
ですが、これは、Optional-Binding と呼ばれており、Optional の Unwrap を行っています。
Optional とは変数にnil
の代入を許容するデータ型で、反対に Not Optional はnil
を代入できません。Optional の変数にはデータ型の最後に?
か!
をつけます。
Unwrap とは Optionalから Not Optional な値を取り出す事です。
あとはベタに移植すれば大丈夫です。
public override void DidFinishProcessingPhoto (AVCapturePhotoOutput captureOutput, CMSampleBuffer photoSampleBuffer, CMSampleBuffer previewPhotoSampleBuffer, AVCaptureResolvedPhotoSettings resolvedSettings, AVCaptureBracketedStillImageSettings bracketSettings, NSError error) { if (photoSampleBuffer != null) photoData = AVCapturePhotoOutput.GetJpegPhotoDataRepresentation (photoSampleBuffer, previewPhotoSampleBuffer); else Console.WriteLine ($"Error capturing photo: {error.LocalizedDescription}"); }
DidFinishRecordingLivePhotoMovie
4つ目です。livePhotoCaptureHandler
に対応するC#のデリゲートはcapturingLivePhoto
です。
Swift
func photoOutput(_ output: AVCapturePhotoOutput, didFinishRecordingLivePhotoMovieForEventualFileAt outputFileURL: URL, resolvedSettings: AVCaptureResolvedPhotoSettings) { livePhotoCaptureHandler(false) }
public override void DidFinishRecordingLivePhotoMovie (AVCapturePhotoOutput captureOutput, NSUrl outputFileUrl, AVCaptureResolvedPhotoSettings resolvedSettings) { capturingLivePhoto (false); }
DidFinishProcessingLivePhotoMovie
5つ目です。これもベタに移植するだけです。
Swift
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingLivePhotoToMovieFileAt outputFileURL: URL, duration: CMTime, photoDisplayTime: CMTime, resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) { if error != nil { print("Error processing live photo companion movie: \(String(describing: error))") return } livePhotoCompanionMovieURL = outputFileURL }
public override void DidFinishProcessingLivePhotoMovie (AVCapturePhotoOutput captureOutput, NSUrl outputFileUrl, CMTime duration, CMTime photoDisplayTime, AVCaptureResolvedPhotoSettings resolvedSettings, NSError error) { if (error != null) { Console.WriteLine ($"Error processing live photo companion movie: {error.LocalizedDescription})"); return; } livePhotoCompanionMovieUrl = outputFileUrl; }
DidFinishCapture
6つ目です。こちらはoptions.uniformTypeIdentifier = self.requestedPhotoSettings.processedFileType.map { $0.rawValue }
で iOS11 からのAPI、processedFileTypeが使われていました。
Swift
func photoOutput(_ output: AVCapturePhotoOutput, didFinishCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) { if let error = error { print("Error capturing photo: \(error)") didFinish() return } guard let photoData = photoData else { print("No photo data resource") didFinish() return } PHPhotoLibrary.requestAuthorization { [unowned self] status in if status == .authorized { PHPhotoLibrary.shared().performChanges({ [unowned self] in let options = PHAssetResourceCreationOptions() let creationRequest = PHAssetCreationRequest.forAsset() options.uniformTypeIdentifier = self.requestedPhotoSettings.processedFileType.map { $0.rawValue } creationRequest.addResource(with: .photo, data: photoData, options: options) if let livePhotoCompanionMovieURL = self.livePhotoCompanionMovieURL { let livePhotoCompanionMovieFileResourceOptions = PHAssetResourceCreationOptions() livePhotoCompanionMovieFileResourceOptions.shouldMoveFile = true creationRequest.addResource(with: .pairedVideo, fileURL: livePhotoCompanionMovieURL, options: livePhotoCompanionMovieFileResourceOptions) } }, completionHandler: { [unowned self] _, error in if let error = error { print("Error occurered while saving photo to photo library: \(error)") } self.didFinish() } ) } else { self.didFinish() } } }
iOS10 対応のサンプルを探したところ以下のようになっていましたので、こちらを移植していきます。
Swift iOS10対応
func capture(_ captureOutput: AVCapturePhotoOutput, didFinishCaptureForResolvedSettings resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) { if let error = error { print("Error capturing photo: \(error)") didFinish() return } guard let photoData = photoData else { print("No photo data resource") didFinish() return } PHPhotoLibrary.requestAuthorization { [unowned self] status in if status == .authorized { PHPhotoLibrary.shared().performChanges({ [unowned self] in let creationRequest = PHAssetCreationRequest.forAsset() creationRequest.addResource(with: .photo, data: photoData, options: nil) if let livePhotoCompanionMovieURL = self.livePhotoCompanionMovieURL { let livePhotoCompanionMovieFileResourceOptions = PHAssetResourceCreationOptions() livePhotoCompanionMovieFileResourceOptions.shouldMoveFile = true creationRequest.addResource(with: .pairedVideo, fileURL: livePhotoCompanionMovieURL, options: livePhotoCompanionMovieFileResourceOptions) } }, completionHandler: { [unowned self] success, error in if let error = error { print("Error occurered while saving photo to photo library: \(error)") } self.didFinish() } ) } else { self.didFinish() } } }
いくつか、C#erにはなじみのない表現が使われています。
まず、guard
ですが、guard let photoData = photoData else {}
は、アンラップとnilチェックを同時に行い、アンラップしたphotoData
をguard~else
ブロック外で使用できます。
※アンラップとは、nil
を代入できるオプショナル型から値を取り出すことです。
もう一つ、[unowned self]
は、非所有参照でself
をキャプチャします。これを使うと、クロージャー内ではクロージャ外のself
とは別の非所有参照のselfを使うのため循環参照が起こりません。
これは、移植時にはメモリリーク防止のおまじないとでも認識しておけば十分です。
PerformChanges
メソッドを確認すると以下のようになっていますので、APIにあわせて実装していきます。
public virtual void PerformChanges(Action changeHandler, Action<bool, NSError> completionHandler);
public override void DidFinishCapture(AVCapturePhotoOutput captureOutput, AVCaptureResolvedPhotoSettings resolvedSettings, NSError error) { if (error != null) { Console.WriteLine($"Error capturing photo: {error.LocalizedDescription})"); DidFinish(); return; } if (photoData == null) { Console.WriteLine("No photo data resource"); DidFinish(); return; } PHPhotoLibrary.RequestAuthorization(status => { if (status == PHAuthorizationStatus.Authorized) { PHPhotoLibrary.SharedPhotoLibrary.PerformChanges(() => { var creationRequest = PHAssetCreationRequest.CreationRequestForAsset(); creationRequest.AddResource(PHAssetResourceType.Photo, photoData, null); var url = livePhotoCompanionMovieUrl; if (url != null) { var livePhotoCompanionMovieFileResourceOptions = new PHAssetResourceCreationOptions { ShouldMoveFile = true }; creationRequest.AddResource(PHAssetResourceType.PairedVideo, url, livePhotoCompanionMovieFileResourceOptions); } }, (success, err) => { if (err != null) Console.WriteLine($"Error occurered while saving photo to photo library: {error.LocalizedDescription}"); DidFinish(); }); } else { DidFinish(); } }); }
これで、コールバックメソッドの実装が完了しました。以上でPhotoCaptureDelegate
の移植が完了です!お疲れさまでした。
最後に完成したコードは以下のようになります。
using System; using Foundation; using AVFoundation; using CoreMedia; using Photos; namespace AVCamSample { public class PhotoCaptureDelegate : AVCapturePhotoCaptureDelegate { public AVCapturePhotoSettings RequestedPhotoSettings { get; private set; } Action willCapturePhotoAnimation; Action<bool> capturingLivePhoto; Action<PhotoCaptureDelegate> completed; NSData photoData; NSUrl livePhotoCompanionMovieUrl; public PhotoCaptureDelegate(AVCapturePhotoSettings requestedPhotoSettings, Action willCapturePhotoAnimation, Action<bool> capturingLivePhoto, Action<PhotoCaptureDelegate> completed) { RequestedPhotoSettings = requestedPhotoSettings; this.willCapturePhotoAnimation = willCapturePhotoAnimation; this.capturingLivePhoto = capturingLivePhoto; this.completed = completed; } void DidFinish() { var livePhotoCompanionMoviePath = livePhotoCompanionMovieUrl?.Path; if (livePhotoCompanionMoviePath != null) { if (NSFileManager.DefaultManager.FileExists(livePhotoCompanionMoviePath)) { NSError error; if (!NSFileManager.DefaultManager.Remove(livePhotoCompanionMoviePath, out error)) Console.WriteLine($"Could not remove file at url: {livePhotoCompanionMoviePath}"); } } completed(this); } public override void WillBeginCapture (AVCapturePhotoOutput captureOutput, AVCaptureResolvedPhotoSettings resolvedSettings) { if (resolvedSettings.LivePhotoMovieDimensions.Width > 0 && resolvedSettings.LivePhotoMovieDimensions.Height > 0) capturingLivePhoto (true); } public override void WillCapturePhoto (AVCapturePhotoOutput captureOutput, AVCaptureResolvedPhotoSettings resolvedSettings) { willCapturePhotoAnimation (); } public override void DidFinishProcessingPhoto (AVCapturePhotoOutput captureOutput, CMSampleBuffer photoSampleBuffer, CMSampleBuffer previewPhotoSampleBuffer, AVCaptureResolvedPhotoSettings resolvedSettings, AVCaptureBracketedStillImageSettings bracketSettings, NSError error) { if (photoSampleBuffer != null) photoData = AVCapturePhotoOutput.GetJpegPhotoDataRepresentation (photoSampleBuffer, previewPhotoSampleBuffer); else Console.WriteLine ($"Error capturing photo: {error.LocalizedDescription}"); } public override void DidFinishRecordingLivePhotoMovie (AVCapturePhotoOutput captureOutput, NSUrl outputFileUrl, AVCaptureResolvedPhotoSettings resolvedSettings) { capturingLivePhoto (false); } public override void DidFinishProcessingLivePhotoMovie (AVCapturePhotoOutput captureOutput, NSUrl outputFileUrl, CMTime duration, CMTime photoDisplayTime, AVCaptureResolvedPhotoSettings resolvedSettings, NSError error) { if (error != null) { Console.WriteLine ($"Error processing live photo companion movie: {error.LocalizedDescription})"); return; } livePhotoCompanionMovieUrl = outputFileUrl; } public override void DidFinishCapture (AVCapturePhotoOutput captureOutput, AVCaptureResolvedPhotoSettings resolvedSettings, NSError error) { if (error != null) { Console.WriteLine($"Error capturing photo: {error.LocalizedDescription})"); DidFinish(); return; } if (photoData == null) { Console.WriteLine("No photo data resource"); DidFinish(); return; } PHPhotoLibrary.RequestAuthorization(status => { if (status == PHAuthorizationStatus.Authorized) { PHPhotoLibrary.SharedPhotoLibrary.PerformChanges(() => { var creationRequest = PHAssetCreationRequest.CreationRequestForAsset(); creationRequest.AddResource(PHAssetResourceType.Photo, photoData, null); var url = livePhotoCompanionMovieUrl; if (url != null) { var livePhotoCompanionMovieFileResourceOptions = new PHAssetResourceCreationOptions { ShouldMoveFile = true }; creationRequest.AddResource(PHAssetResourceType.PairedVideo, url, livePhotoCompanionMovieFileResourceOptions); } }, (success, err) => { if (err != null) Console.WriteLine($"Error occurered while saving photo to photo library: {error.LocalizedDescription}"); DidFinish(); }); } else { DidFinish(); } }); } } }
今回はここまでです。
次回はこちらからどうぞ!
hiro128.hatenablog.jp