個人的なメモ

Tomohiro Suzuki @hiro128_777 のブログです。Xamarin に関する事を中心に書いています。 Microsoft MVP for Development Technologies 2017- 本ブログと所属組織の公式見解は関係ございません。

Swift, Objective-C を Xamarin.iOS に移植する際のポイント(1) デリゲート その2

はじめに

こんにちは、@hiro128_777です。

前回は、Swift のプロトコル、デリゲートが Xamarin.iOS でどのように表現されているかを確認しました。

hiro128.hatenablog.jp


今回は、前回の続きです。実際に Swift のコードを Xamarin.iOS へ移植していきましょう。

PhotoCaptureDelegate.swift の Xamarin.iOS への移植


それではPhotoCaptureDelegate.swiftファイルのコードを見てみましょう。

using

importを確認すると

Swift

import AVFoundation
import Photos

とありますので、追加します。

C#

using System;
using AVFoundation;
using Photos;

クラス定義

次にクラスの定義部分に注目すると、以下のようにクラスの定義とエクステンションがあります。

Swift

class PhotoCaptureProcessor: NSObject {


Swift

extension PhotoCaptureProcessor: AVCapturePhotoCaptureDelegate {


前回説明したように、Swift でAVCapturePhotoCaptureDelegateのように定義済みのプロトコルが利用されている場合、基本的には Xamarin.iOS 側には対応する interface および class が準備されています。
よって、AVCapturePhotoCaptureDelegateのメタ情報を確認すると

C#

public class AVCapturePhotoCaptureDelegate : NSObject, IAVCapturePhotoCaptureDelegate, INativeObject, IDisposable

とありますので、AVCapturePhotoCaptureDelegateを継承すれば、NSObjectを継承し、AVCapturePhotoCaptureDelegateを実装するクラスになります。
よって、以下のようにクラスを定義します。

C#

using System;
using AVFoundation;
using Photos;
using Foundation;

namespace AVCamSample
{
    public class PhotoCaptureDelegate : AVCapturePhotoCaptureDelegate
    {
    }
}

 

フィールド

次に、インスタンス変数(C#ではフィールド)を移植します。

Swift

private(set) var requestedPhotoSettings: AVCapturePhotoSettings

private let willCapturePhotoAnimation: () -> Void

private let livePhotoCaptureHandler: (Bool) -> Void

private let completionHandler: (PhotoCaptureProcessor) -> Void

private var photoData: Data?

private var livePhotoCompanionMovieURL: URL?

Swift では[アクセス修飾子] [var or let] [変数名] : [型名] の順で記述されています。

よって、1行目で言えば、requestedPhotoSettingsが変数名、AVCapturePhotoSettingsが型名です。
これは、C#に簡単に書き換えられます。
AVCapturePhotoSettingsの型も Xamarin.iOS に定義済みです。
もし「型が見つからない」とエラーが出る場合は、using を確認してみてください。

private(set) は、setter のみprivateという意味です。

3行目の場合、willCapturePhotoAnimationが変数名、() -> Voidが型名です。
これは、型名が() -> Voidですから、C#で言うデリゲートですね(Swiftではクロージャ)。->の左辺が引数の型、右辺が戻り値の型です。

9,11行目のData?,URL?は、Xamarin.iOS ではそれぞれNSData,NSUrlになります。
NSがつくのは、Xamarin.iOS は、Objective-C に基づいており、Objective-C で、NSがつく型名になっているからです。
また、NSData,NSUrlを使う場合、using Foundation;が必要になります。

これらを考慮するとフィールド定義は以下のようになります。

C#

using System;
using AVFoundation;
using Photos;
using Foundation;

namespace AVCamSample
{
    public class PhotoCaptureDelegate : AVCapturePhotoCaptureDelegate
    {
        public AVCapturePhotoSettings RequestedPhotoSettings { get; private set; }

        Action willCapturePhotoAnimation;
        Action<bool> capturingLivePhoto;
        Action<PhotoCaptureDelegate> completed;

        NSData photoData;
        NSUrl livePhotoCompanionMovieUrl;
    }
}

 

コンストラク

次に、イニシャライザ(C#ではコンストラクタ)を移植します。

Swift

init(with requestedPhotoSettings: AVCapturePhotoSettings,
     willCapturePhotoAnimation: @escaping () -> Void,
     livePhotoCaptureHandler: @escaping (Bool) -> Void,
     completionHandler: @escaping (PhotoCaptureProcessor) -> Void) {
    self.requestedPhotoSettings = requestedPhotoSettings
    self.willCapturePhotoAnimation = willCapturePhotoAnimation
    self.livePhotoCaptureHandler = livePhotoCaptureHandler
    self.completionHandler = completionHandler
}

引数がクロージャであるものに全て@escapingがついていますが、移植する際にはあまり気にしなくても良いです。
クロージャがスコープから抜けても存在し続けるときに@escapingが必要になります。

selfC#ではthisです。
あとはベタで移植すれば大丈夫です。

これらを考慮しコンストラクタを追加すると以下のようになります。

C#

using System;
using AVFoundation;
using Photos;
using Foundation;

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;
        }

    }
}

コンストラクタは簡単ですね!
 

メソッド

次に、メソッドを移植します。

Swift

private func didFinish() {
    if let livePhotoCompanionMoviePath = livePhotoCompanionMovieURL?.path {
        if FileManager.default.fileExists(atPath: livePhotoCompanionMoviePath) {
            do {
                try FileManager.default.removeItem(atPath: livePhotoCompanionMoviePath)
            } catch {
                print("Could not remove file at url: \(livePhotoCompanionMoviePath)")
            }
        }
    }
    
    completionHandler(self)
}

ここは多少厄介です。なぜなら、Xamarin.iOS は、Objective-C に基づいており、
Swift をそのまま移植できず、若干表現を変えなければならないためです。

ここは、仕方ないので Swift のコードの処理を理解し、同等の処理を Xamarin.iOS で書かなくてはいけません。

まずif letですがこれは、これは、Optional-Binding と呼ばれており、Optional の Unwrap を行っています。

Optional とは変数にnilの代入を許容するデータ型で、反対に Not Optional はnilを代入できません。Optional の変数にはデータ型の最後に?!をつけます。

Unwrap とは Optionalから Not Optional な値を取り出す事です。

FileManagerは、Xamarin.iOS ではNSFileManagerです。
このNSがつくかどうかの件については、慣れるとだんだん迷わなくなります。

最初のうちは「型が見つからない」とエラーが出る場合は、まずは using を確認、
次に、NSをつけてみるという手順を取るとつまづきにくいです。

後は、インテリセンスを利用してNSFileManagerAPIにあわせて、書き換えていきます。

これらを考慮しDidFinish()を追加すると以下のようになります。

C#

using System;
using AVFoundation;
using Photos;
using Foundation;

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);
        }

    }
}

これでクラス本体部分の移植が完了しました!
いかがでしょうか?最初はちょっとわかりにくいかもしれませんが、何度か試してみれば慣れますので、ぜひ試してみてください!

また、長くなりましたので今回はここまでです。

間違いなどございましたらご指摘お願いします。

次回は、エクステンション部分を移植します。
 


次回はこちらからどうぞ!
hiro128.hatenablog.jp