Xamarin.iOSにObjective-Cライブラリをバインディングする際にハマったこと

私がXamarin.iOSアプリにObjective-Cライブラリをバインディングする際にハマった点と解決手段を[公式リファレンス](https://developer.xamarin.com/guides/ios/advanced_topics/binding_objective-c/walkthrough/ "公式リファレンス")に沿ってご紹介します。

# この記事を作成した動機
Objective-Cライブラリをバインディングする方法について、公式リファレンスに解説があるのですが、ツールなどのバージョンアップの影響か、記載されているとおりにしてもライブラリを動かすことができませんでした。そこで、私と同様につまづいた方の参考用として記載します。

# 使用環境
- macOS High Sierra 10.13.1
- Xcode 9.1
- xcodebuild 9.1 Build version 9855
- Visual Studio Community 2017 for Mac 7.2.2(build 11)

# 概要
リファレンスの内容に入っていく前に、簡単にObjective-Cライブラリをバインディングする流れをご説明します。

Objective-Cライブラリをバインディングする流れは以下のとおりです。
1. Visual StudioでXamarin.iOSバインディングプロジェクトを作成します
2. ビルド済みのObjective-Cライブラリをバインディングプロジェクトに追加します
3. Objective SharpieでObjective-CライブラリのAPIをラッピングするコードを生成します
4. 自動生成されたコードをコンパイルが通るように修正します
5. Xamarin.iOSプロジェクトを追加します
6. Xamarin.iOSプロジェクトからバインディングプロジェクトを参照します
7. Xamarin.iOSプロジェクトからObjective-CライブラリのAPIを呼び出します

# バインディング方法
では先述の[公式リファレンス](https://developer.xamarin.com/guides/ios/advanced_topics/binding_objective-c/walkthrough/ "公式リファレンス")に沿ってご説明していきます。細かい部分は省略していますので、もっと詳しく知りたい方は元の文章で補完されることをお勧めします。
（私がハマったポイントなどは引用文の文体で記載します）

## Overview
Xamarin.iOSアプリケーションを開発する際に、サードパーティー製のObjective-Cライブラリを利用することができます。

iOSエコシステムには以下の３種類のライブラリがあります。
- Static Library
- フレームワーク
- ソースコード

ここでは[InfColorPicker](https://github.com/InfinitApps/InfColorPicker "InfColorPicker")ライブラリを例にソースコードのケースで説明します。InfColorPickerのページからmasterブランチをcloneして利用してください。

> フレームワークの場合、フレームワークのバイナリファイルに通常は拡張子が付いていませんが、Visual Studioに追加する際には「.a」という拡張子が必要となりますので、ファイルをリネームしてください。リネームしないと、Static Libraryと解釈されず、ただのファイルとして扱われます。

## Requirements
- XcodeとiOS SDK
- Xcodeコマンドラインツール
- Visual Studio for Mac
- Objective Sharpieの最新版

>これらのインストールについては特に問題ありませんでした。開発業務に携わっている方であれば困らないと思いますので、ここでは割愛します。

# Walkthrough
## Create a Static Library
通常のiOSライブラリ開発と同様にしてXcodeまたはxcodebuildでStatic Libraryをビルドしてください。

## Create a Xamarin.iOS Binding Project
1．Visual Studio for MacでFile > New Solutionを選択してください
2．iOSカテゴリ中のLibraryを選択後、Bindings Libraryを選択して決定してください

### Including the Static Library in the Binding Project
ビルドしておいたStatic LibraryをBinding Projectに追加します。

> Static Libraryを追加すると、自動的に.linkwith.csという拡張子のファイルが.aファイルの枝に追加されます。追加したStatic Libraryに依存Frameworkなどがある場合はこの.linkwith.csファイルで指定します。今回のケースでは変更する必要はありません。
なお、C++ライブラリの場合は.linkwith.csファイルにIsCxx = trueを指定する必要があります。また、コンパイルオプションをLinkerFlags = "-lc++"などとして指定できます。

.
> 公式リファレンスではNative Referenceから右クリックして追加する手順となっていますが、それだと.linkwith.csファイルが作成されません。Binding Projectのルートノード部分で右クリックしてAdd Files...メニューから追加する必要があります。

.
> Static Libraryからさらに別の独自Static Libraryを参照する場合は、単純に同じバインディングプロジェクトにそのStatic Libraryも追加してあげれば、Objective-Cライブラリ側から参照できます。

## Using Objective Sharpie
Objective SharpieはObjective-CライブラリをC#にバインドするための定義をヘッダファイルから自動生成できるコマンドラインツールです。自動生成された定義はそのままではコンパイルできないことが多いため、手編集が必要です。

1. 以下のコマンドで利用可能なiOS SDKを確認します。  
```sharpie xcode -sdks```  
iphoneosから始まる値（iphoneos9.3など）をメモに控えておいてください。
2. 以下のコマンドで定義を生成します。
```sharpie bind -output Output -namespace [a] -sdk [b] -scope [c] [d]```

a: ライブラリをC#コードから呼び出す際に使用するネームスペース
b: 手順1で控えて置いた値
c: Objective-Cライブラリのヘッダファイルが格納されているパス
d: Objective-Cライブラリのヘッダファイル

例： sharpie bind -output Output -namespace InfColorPicker -sdk iphoneos9.3 -scope InfColorPicker/Headers InfColorPicker/Headers/*.h

>この手順が最もハマりました。公式リファレンスでは-scopeオプションがありませんが、-scopeオプションを付けないと58kstepほどの定義が生成され、大量のコンパイルエラー指摘に途方に暮れることになります。

>公式リファレンスではヘッダファイルの指定に、他のヘッダファイルを参照しているトップレベルのファイルを指定すべきと記載されていますが、*.hと特に違いはありませんでした。

この結果、ApiDefinitions.csとStructsAndEnums.csというファイルがOutputディレクトリに生成されます。

この二つのファイルをバインディングプロジェクトのトップレベルに追加します。
追加したら、ApiDefinitions.csのPropertiesを開き、Build ActionをObjcBindingApiDefinitionに設定します。また、StructsAndEnums.csのPropertiesを開き、Build ActionをObjcBindingCoreSourceに設定します。
バインディングプロジェクトを作成した際にもともとあったApiDefinitions.csとStructs.csは不要ですので削除して構いません。

> ApiDefinitions.csファイルの中でコンパイルエラーとなっている、Verify属性が付いている行はここではすべて削除しても問題ありません。
> StructsAndEnums.cs内でコンパイルエラーとなっている行はすべて削除して問題ありません。

## Normalize the API Definitions
Objective SharpieではDelegateの変換でトラブルが起こりやすいです。リファレンスで使用しているInfColorPickerの場合は、生成されたApiDefinitions.csに定義されているInfColorPickerControllerDelegateインターフェースの属性を[Protocol, Model]から以下に修正する必要があります。

```
[BaseType(typeof(NSObject))]
[Model]
```

## Using the Binding
作成したバインディングライブラリを使ったiPhoneアプリを作成します。

1. Xamarin.iOSプロジェクトを作成します
2. 作成したプロジェクトのReferencesをダブルクリックして、Projectsタブからバインディングライブラリを選択します
3. Xamarin.iOSプロジェクトのMain.storyboardを開き、Buttonを画面に追加します
4. ButtonのPropertiesでNameをChangeColorButtonに、TitleをChange Background Colorにします
5. InfColorPickerプロジェクト内にあるInfColorPickerView.xibファイルをXamarin.iOSプロジェクトのトップレベルに追加します

### Protocols and Xamarin.iOS
Objective-Cにはプロトコルと呼ばれる、C#のインターフェースに似た概念があります。インターフェースとの大きな違いは、Objective-Cでは@optionalというキーワードでどのメソッドが省略可能か指定できるところです。

### Implementing a Strong Delegate
新規にColorSelectedDelegate.csクラスをXamarin.iOSプロジェクトに追加します。
ColorSelectedDelegate.csの内容は以下にします。

```objc
using InfColorPickerBinding;
using UIKit;

namespace InfColorPickerSample
{
    public class ColorSelectedDelegate:InfColorPickerControllerDelegate
    {
        readonly UIViewController parent;

        public ColorSelectedDelegate (UIViewController parent)
        {
            this.parent = parent;
        }

        public override void ColorPickerControllerDidFinish (InfColorPickerController controller)
        {
            parent.View.BackgroundColor = controller.ResultColor;
            parent.DismissViewController (false, null);
        }
    }
}
```

次に、ViewController.csファイルを開き、以下の様に修正します。

```objc
using System;
using UIKit;
using InfColorPicker;

namespace InfColorPickerSample
{
  public partial class ViewController : UIViewController
  {
		ColorSelectedDelegate selector;
    protected ViewController(IntPtr handle) : base(handle)
    {
      // Note: this .ctor should not contain any initialization logic.
    }

    public override void ViewDidLoad()
    {
      base.ViewDidLoad();
      // Perform any additional setup after loading the view, typically from a nib.
      ChangeColorButton.TouchUpInside += HandleTouchUpInsideWithStrongDelegate;
      selector = new ColorSelectedDelegate(this);
    }

    public override void DidReceiveMemoryWarning()
    {
      base.DidReceiveMemoryWarning();
      // Release any cached data, images, etc that aren't in use.
    }

    private void HandleTouchUpInsideWithStrongDelegate(object sender, EventArgs e) {
      InfColorPickerController picker = InfColorPickerController.ColorPickerViewController;
      picker.Delegate = selector;
      picker.PresentModallyOverViewController(this);
    }
  }
}
```

以上です。コード署名を設定してアプリのビルドが成功すれば、Change Background Colorというボタンだけが表示されるアプリができます。そのボタンを押すと、背景色を変更できる画面が表示されます。適当な色を選んで元の画面に戻ったとき、背景色が変わっていればバインディングが成功しています。

お疲れ様でした。