はじめに
こんにちは、@hiro128_777です。
前回は、Swift のコードPhotoCaptureDelegate.swift
のコールバックメソッドの定義部分を移植しました。
hiro128.hatenablog.jp
今回は、前回の続きです。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
ですので、そこだけ気をつけて書き換えれば、後はベタに移植するだけです。
C#
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()
}
C#
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 な値を取り出す事です。
あとはベタに移植すれば大丈夫です。
C#
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)
}
C#
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
}
C#
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にあわせて実装していきます。
C#
public virtual void PerformChanges(Action changeHandler, Action<bool, NSError> completionHandler);
C#
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
の移植が完了です!お疲れさまでした。
最後に完成したコードは以下のようになります。
C#
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