Xamarin.Forms for iOSがStoryboardと仲良くする方法（そしてiOSのネイティブバインディングについて）

この記事は [Xamarin Advent Calendar 2018](https://qiita.com/advent-calendar/2018/xamarin) の13日目の記事です。
また、[[初心者さん・学生さん大歓迎！] Xamarin その1 Advent Calendar 2017](https://qiita.com/advent-calendar/2017/xamarin-student) の16日目の記事の続きです。

## Xamarin.Forms.Platform.iOS と Storyboardと

動機は単純です。
きっちりPage設計とかするのが面倒なときがあるんです。

### というわけで、実装

**前回のfor Macと同じことをやればできます！**
逆になぜ前回出来なかったんだろう…？

1. Xamarin.iOSのプロジェクトにNuGetからXamarin.Formsを追加
2. `AppDelegate.cs` のクラスを `UIApplicationDelegate` から `FormsApplicationDelegate` に変更
3. `FinishedLaunching` 関数以外を削除
4. `FinishedLaunching` 内の追記と `Application` 子クラスの追加

```csharp:AppDelegate.cs
        public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
        {
            // Override point for customization after application launch.
            // If not required for your application you can safely delete this method

            Xamarin.Forms.Forms.Init();
            LoadApplication(new App());

            return true;
        }
```

この関数以外を削除することで、App内の `OnStart` `OnPause` `OnResume` が呼び出される様になります。

```sharp:App.cs
    public class App : Xamarin.Forms.Application
    {
        public App()
        {
        }
    }
```

これで、Storyboardを使いつつ、Xamarin.Formsが使えます！

## 次にiOSのネイティブバインディング

これだけだと味気なさすぎなので。
せっかくのAdvent Calendarなので、ここ最近で自分がやったもう一つのネタ、iOSのネイティブバインディングについて書いておきます。

### 先ずはCoreFoundation

漢字→ローマ字などができたりする機能、 `CFStringTokenizerCreate` などはXamarin内で定義されていません。
使うには以下の様にします。

```csharp:宣言
[Flags]
enum CFStringTokenizerUnit
{
    Word = 0,
    Sentence = 1,
    Paragraph = 2,
    LineBreak = 3,
    WordBoundary = 4,
    AttributeLatinTranscription = 1 << 16,
    AttributeLanguage = 1 << 17,
}

[DllImport("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", CharSet = CharSet.Unicode)]
private static extern IntPtr CFLocaleCopyCurrent();

[DllImport("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", CharSet = CharSet.Unicode)]
private static extern IntPtr CFStringTokenizerCreate(IntPtr allocator, IntPtr str, CFRange range, CFStringTokenizerUnit flags, IntPtr locale);
```

```csharp:使用
var locale = CFLocaleCopyCurrent();
var text = "今日はいい天気ですね";
var tokenizer = CFStringTokenizerCreate(IntPtr.Zero, new NSString(text).Handle, new CFRange(0, text.Length), CFStringTokenizerUnit.WordBoundary, locale);
```

基本的にObjective-CのクラスオブジェクトはIntPtrで宣言しておき、obj.Handleで指定します。

### 次は、Selectorを使用

Objective-Cで書いたものと、C#からSelector呼び出しに書き直したものです。

```objc:Objective-C
NLTokenizer* tokenizer = [[NLTokenizer alloc] initWithUnit:NLTokenUnitWord];
tokenizer.string = @"今日はいい天気ですね";
NSArray* res = [tokenizer tokensForRange:NSMakeRange(0, tokenizer.string.length)];
```

```csharp:C#
var text = "今日はいい天気ですね";
var obj = IntPtr_objc_msgSend(Class.GetHandle("NLTokenizer"), Selector.GetHandle("alloc"));
var initedObj = IntPtr_objc_msgSend_IntPtr(obj, Selector.GetHandle("initWithUnit:"), (IntPtr)0);
void_objc_msgSend_IntPtr(initedObj, Selector.GetHandle("setString:"), new NSString(text).Handle);
var tokens = IntPtr_objc_msgSend_NSRange(initedObj, Selector.GetHandle("tokensForRange:"), new NSRange(0, text.Length));
var tokensObj = Runtime.GetNSObject(tokens);


[DllImport(Constants.ObjectiveCLibrary, EntryPoint = "objc_msgSend")]
static extern void void_objc_msgSend_IntPtr(
    IntPtr receiver,
    IntPtr selector,
    IntPtr arg
);

[DllImport(Constants.ObjectiveCLibrary, EntryPoint = "objc_msgSend")]
static extern IntPtr IntPtr_objc_msgSend(
    IntPtr receiver,
    IntPtr selector
);

[DllImport(Constants.ObjectiveCLibrary, EntryPoint = "objc_msgSend")]
static extern IntPtr IntPtr_objc_msgSend_IntPtr(
    IntPtr receiver,
    IntPtr selector,
    IntPtr arg
);

[DllImport(Constants.ObjectiveCLibrary, EntryPoint = "objc_msgSend")]
static extern IntPtr IntPtr_objc_msgSend_NSRange(
    IntPtr receiver,
    IntPtr selector,
    NSRange range
);
```

クラスメソッドとインスタンスメソッドを出すために `alloc` まで同じパターンにしてみました。
なんとな〜くの流れでわかると思いますが、かなり冗長になりますね。
関数宣言ありますし。引数型のパターン毎に宣言しないといけないし。
ただ、なんでもこい、なので、PrivateFramework内のものでも呼べます。

あ、もちろん、 `NLTokenizer` はXamarinで宣言されているので下記で大丈夫ですよ。

```csharp
var tokenizer = new NLTokenizer(NLTokenUnit.Word);
tokenizer.String = "今日はいい天気ですね";
var tokens = tokenizer.GetTokens(new NSRange(0, tokenizer.String.Length));
```

### ライブラリを使う

.a形式（iOSの場合）をリンクしたライブラリ（.dll）ファイルを作成し、プロジェクトで参照して使用することができます。
.aファイルは.dllファイルのリソース内に保持されて使用されるようです。

今回は詳しく書きませんが、 `btouch-native` というコマンドを使用して.dllファイルを作成します。
→[サンプルページ](https://developer.xamarin.com/samples/monotouch/BindingSample/)&nbsp;&nbsp;→[サンプルソースコード](https://github.com/xamarin/ios-samples/tree/master/BindingSample)

## さいごに

最後の方は駆け足にしてしましましたが、Xamarinはバージョンを重ねるごとに作りやすく使いやすくなっていく印象があります。
使ったことない方も一度使ってみるのはどうでしょうか。