React NativeでiOSのExternal Link Account APIに対応する

## 🔰 はじめに
2022年3月あたりに、iOSのリーダーAppにおいてアカウント登録などを目的とする外部サイトへのリンクを設置することが可能となりました。
それに伴い、External Link Acccount APIというものを使用する必要があるのですが、React Nativeでの実装方法が調べても見つからなかったため、備忘録and誰かの参考になればと思い手順を書くことにしました。
なお、下記の手順の前にXcode側の設定が諸々必要なのですが、[詳細な公式ドキュメント](https://developer.apple.com/jp/support/reader-apps/)がすでに存在しているので、こちら参考にしていただければと思います！
今回はXcode側の設定が終わった後の、外部リンク用モーダルシートを開く方法のみ書くことにします！

## 🏁 この記事のゴール
React NativeのiOSアプリで:point_down_tone5:のモーダルシートを開く（以下`外部リンク用モーダルシート`とします）
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616804/7cf97b79-6c74-a2cc-0438-4792baa0686f.png" width="200">

## 📱 ネイティブ側の実装
今回はネイティブ側のコードを呼び出す必要があるため、Native Modulesという機能を利用していきます。
Native ModulesはReact NativeからiOSやAndroidのAPIを実行できる機能で、React Nativeでは用意されていないけど、ネイティブ側の機能を使いたいという時に登場するものです。
実装するにあたってSwift、Objective-C、Briding-Headerの3ファイルが必要なので、以下順を追って説明いたします。

### Swiftファイルの作成
まずは、外部リンク用モーダルシートを開くための、Swiftファイルを作成していきます。
Xcodeのプロジェクトファイルを開き、プロジェクトのディレクトリを右クリック->New Fileをクリックし、中からSwift Fileを選択します。
Swiftファイルが作成されると思うので、ファイル名をExternalLinkAccount.swiftなどに変更し、以下のコードを記述していきます。
```swift:ExternalLinkAccount.swift
import Foundation
import StoreKit

@objc(ReactNativeExternalLinkAccount)
class ReactNativeExternalLinkAccount: NSObject {

  @objc
  func open() {
    if #available(iOS 16.0, *) {
      Task {
        if await ExternalLinkAccount.canOpen {
          do {
            try await ExternalLinkAccount.open()
          } catch {
            print("Error - \(error)")
          }
        }
      }
    }
  }
}

```
中身はExternal Link Account APIのopenメソッドを実行しているだけのシンプルなコードです。
APIに関連するAppleの公式ドキュメントは[こちら](https://developer.apple.com/documentation/storekit/external_link_account)。
クラス名は分かりやすいように`ReactNativeExternalLinkAccount`にし、メソッド名は`open`にしております。
補足として、iOS16以上にしか対応していないため、上記のような条件分岐になっております。（iOS15以下の対応に関しては、後ほど記載いたします）

### Objective-Cファイルの作成
Swiftで作成した関数等をReact(JS)側で呼び出し可能とするために、Objective-Cでエクスポートするコードを書いていきます。
Swiftの時と同じくプロジェクトディレクトリを右クリック->New Fileをクリックし、今回はObjective-C Fileを選択します。
ファイル名をExternalLinkAccount.mなどに変更し以下のコードを記載します。
```objc:ExternalLinkAccount.m
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(ReactNativeExternalLinkAccount, NSObject)
  RCT_EXTERN_METHOD(open)
@end
```
`RCT_EXTERN_MODULE`と`RCT_EXTERN_METHOD`を使用し、先ほど作成した`ReactNativeExternalLinkAccount`クラスと`open`メソッドをエクスポートさせることで、React（JS）側から呼ぶことが可能となります。

### Bridging-Headerファイルの作成
SwiftとObjective-Cのコードをブリッジさせるために必要なファイルです。
Swiftファイル作成時に自動で生成されている場合もあるのですが、もし存在していない場合は新規に作成する必要があります。
プロジェクトディレクトリを右クリック->New Fileをクリックし、Header Fileを選択します。
気を付ける点として、ファイル名称は`{プロジェクト名}-Bridging-Header.h`という形式にしてください。（ここでは適当にMyProjectとしています）
中身は以下のような一文でOKです。
```objc:MyProject-Bridging-Header.h
#import "React/RCTBridgeModule.h"
```

## 💻 React側の実装
上記の手順でネイティブ側の準備はできたので、今度はReact(JS)側で呼び出す処理を組み込んでいきます。
外部リンク用モーダルシートを開く用のボタンを用意し、タップイベントに以下の処理を書いていきます。

```react:App.tsx
import { NativeModules } from 'react-native';

const openExternalLinkAccountModal = () => {
  NativeModules.ReactNativeExternalLinkAccount.open();
}
```
React側の記述は簡単で、Native Modulesというオブジェクトがすでに用意されているので、それを使用します。
Objective-Cファイルで外部参照しているReactNativeExternalLinkAccountというモジュールを読み込み、その中のopen関数を実行して、外部リンク用モーダルシートを開いているという内容になっております。
ここまで実装していれば、iOS16以上の端末ならばモーダルシートが開けるようになっているはずです！

## 🧷 iOS15以下の対応
iOS15以下はAPIが未対応のため、自前でモーダルシートを作成する必要があります。
とは言っても、ガイドラインがガチガチに決まっているので、あまり迷うことなく実装していけるかと思います。
ガイドラインは[公式](https://developer.apple.com/jp/support/reader-apps/)の`アプリ内モーダルシートの実装`->`設計仕様（5 MB）をダウンロード(英語)`からDL可能です。
アプリ側で、どうやってiOS16以上とそれ以下を判断するかに関してですが、私はReact Native側で条件分岐することにしました。
```react:App.tsx
import { NativeModules, Platform } from 'react-native';

const openExternalLinkAccountModal = () => {
  const majorVersionIOS = parseInt(Platform.Version, 10);
  // iOS16以上は、ネイティブのExternalLinkAccountを使用する。iOS15以下は自前のモーダルシートを開く。
  if (majorVersionIOS >= 16) {
    NativeModules.ReactNativeExternalLinkAccount.open();
  } else {
    setIsShowExternalLinkAccountModal(true);
  }
}
```
`Platform.Version`でiOSのバージョンを取得できるので、それを元に条件分岐しております。
React側ではなく、Swiftコード側で判断する実装方法もあるのですが、そこはお任せします！

## 📝 まとめ
- Native Modulesを使用することで、React NativeでもExternal Link Account APIを実装することができる
- iOS16以上と15以下で実装方法が異なるので、注意が必要

## 🔗 参考文献
https://developerinsider.co/external-link-account-implementation-guide-reader-apps/