Xamarin.iOS のネイティブバインディングを活用して既存の Objective-C 資産を流用する
Xamarin.iOS では、ネイティブバインディングを使用して Objective-C で書かれたライブラリを利用することができます。
つまり、既存のObjective-Cで書かれたアプリをXamarinに移行する際、特定の機能はネイティブライブラリにすることでObjective-Cのまま利用することができます。
ここでは、Objective-Cで書かれたアプリをUIViewControllerごとネイティブライブラリにし、Xamarin.iOSにネイティブバインディングで持ってくる方法をご紹介します。
プロジェクトの作成
作成したネイティブライブラリを実際に動作させて確認する確認用アプリを作り、その中に今回の目標物を生成するスタティックライブラリプロジェクトを追加し、Objective-Cで確認してからXamarinに持って行くと効率的です。
Xcode プロジェクト作成
まず、Xcode で Empty Application を作成します。
次に、作成したプロジェクトを右クリックし、New Project... を選択します。
今度は Cocoa Touch Static Library を指定します。これがXamarinに持って行くスタティックライブラリになります。
次に、スタティックライブラリプロジェクトの Build Settings にある、Build Active Architecture Only の指定を削除し、No にします。
リソースバンドル作成ターゲットの追加
次に、このライブラリが使用する画像・テキストなどのリソースが含まれるバンドルを作成します。
追加したネイティブライブラリのプロジェクトを選択し、ターゲットのリストの下にある追加ボタンをクリックします。
iOS のほうには Bundle がないため、OS X のほうで追加してから iOS に変更するという手順を踏みます。OS X の Frameworks & Library から Bundle を選択します。このとき、名前は ネイティブライブラリの名前 + Resources という形にしてください。(この後で追加するスクリプトがそれを前提に作られています。)
Build Settings で Base SDK に設定されている OS X の指定を delete キーで消します。こうすることでプロジェクト側に指定されている iOS の指定が使用されるようになります。
次に、スタティックライブラリのターゲットを選び、Build Phases のページにある Target Dependencies に、先ほどのバンドルを指定します。これで、スタティックライブラリのビルド時にリソースのバンドルが同時に作成されるようになります。
ユニバーサルバイナリ作成のターゲット追加
通常、iOS アプリやライブラリは、実機実行するときは実機用、シミュレータ実行するときはシミュレータ用のライブラリのみが出力されます。これではシミュレータ実行時と実機実行時で別々のDLLを参照しないといけなくなって不便なため、スクリプトを追加して実機用とシミュレータ用のスタティックライブラリをあわせて1つのスタティックライブラリにする処理をプロジェクトに追加します。
先ほどBundleを追加したときと同じ方法で、iOS の Other にある、Aggregate を選択します。自分はライブラリ名 + Universal と名前をつけることが多いです。
Aggregate の Build Phase に Run Script Build Phase を追加します。
追加されたRun Script Build Phaseに以下のスクリプトを入力します。
# define output folder environment variable LIBRARY_TARGET_NAME=${PROJECT_NAME} UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal # Step 1. Build Device and Simulator versions xcodebuild -target "${LIBRARY_TARGET_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" xcodebuild -target "${LIBRARY_TARGET_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator -arch i386 BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" # make sure the output directory exists mkdir -p "${UNIVERSAL_OUTPUTFOLDER}" # Step 2. Create universal binary file using lipo lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/lib${PROJECT_NAME}.a" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/lib${PROJECT_NAME}.a" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/lib${PROJECT_NAME}.a" # Last touch. copy the header files. Just for convenience cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/include" "${UNIVERSAL_OUTPUTFOLDER}/" cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${LIBRARY_TARGET_NAME}Resources.bundle" "${UNIVERSAL_OUTPUTFOLDER}/" open "${UNIVERSAL_OUTPUTFOLDER}/"
※このスクリプトはCreating a Static Library in iOS Tutorial | Ray Wenderlichに掲載されていたものに少し手を入れたものです。
確認用アプリの設定
確認用アプリから作成したスタティックライブラリを利用できるように設定します。
確認用アプリの Build Phases で、以下のような設定を行います。
- Target Dependencies
- スタティックライブラリ、バンドルを追加します。
- Link Binary With Libraries
- スタティックライブラリを指定します。
- Copy Bundle Resources
- バンドルを追加します。
- ここでは追加ボタンから参照できないことがあります。そのときは Project Navigator の Products にあるバンドルの項目をドロップして追加してください。
既存アプリのソースのプロジェクトへの追加
あとは既存のソースをスタティックライブラリのプロジェクトに追加していきます。
ソースコードはスタティックライブラリ、リソースはバンドルをターゲットに指定します。
また、既存のソースで使用しているフレームワークなどは別途スタティックライブラリに追加しておきます。
追加したソースの調整
追加したソースのうち、スタティックライブラリにしたことによる調整と、Xamarin側から呼び出すときのインターフェイスを整備します。
リソースの読み込みの修正
リソースの読み込み部分について、下記のように修正します。
- NSBundle を指定した読み込みの場合、[NSBundle mainBundle] や nil を指定している部分にライブラリのバンドルを明示します。
- [UIImage imageNamed:] を使用しているところは、ファイル名の先頭にバンドル名を指定します。 ** [UIImage imageNamed:@"hoge.png"] → [UIImage imageNamed:@"EditorResources.bundle/hoge.png"]
バンドルを使用するときは、下記のようなクラスメソッドを用意すると便利です。
static NSBundle *editor_SharedBundle = nil; + (NSBundle *)bundle { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ editor_SharedBundle = [NSBundle bundleWithURL: [[NSBundle mainBundle] URLForResource:@"EditorResources” withExtension:@"bundle"]]; }); return editor_SharedBundle; }
Xamarin 側から呼び出すインターフェイスを用意する
自分で作るライブラリは外部に公開するインターフェイスを自由に作ることができるので、Objective Sharpieに処理させる部分を最小にすることができれば、ネイティブバインディングを作成するときに手間が少ないです。
そこで、画面ベースで持ってくるのであれば当該のUIViewControllerを作成するクラスだけを公開するとよいでしょう。
#import "Editor.h" #import <UIKit/UIKit.h> @implementation Editor + (UIViewController *) editorViewController; @end
プロジェクト内での作業
プロジェクト内で作業を行うときは、ターゲットの指定を以下のように切り替えます。
- 動作確認をするときはサンプルアプリ
- スタティックライブラリを作成するときは Universal の Aggregate
- このとき、ターゲットデバイスは iOS Device に合わせるようにしてください。
ネイティブバインディングの作成
ユニバーサルライブラリを生成すると、生成されたファイルが格納されているフォルダが表示されます。
この中の .a ファイルを iOS Binding Project へ追加し、.h ファイルを Objective Sharpie に読み込ませて定義生成します。
ネイティブバインディングライブラリができたら、アプリのプロジェクトに参照設定で追加し、別途出力されているbundleもプロジェクトにリソースとして追加します。
おわりに
既存のプロジェクトをXamarinへ移行する際は、一気にすべてC#でリプレイスする以外にこのようなネイティブライブラリを使用して既存のコードを活用しながら少しずつすすめる方法もあります。プロジェクトの移行の際は検討してはいかがでしょうか。