0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【React Native iOS】外部アプリからファイルを開く方法

Posted at

※注意:この記事はiOSのことだけ書いてます。

先に結論

  1. Document Typesを設定する
  2. Linkingを使ってファイルパスを取得する

以上。

何がしたいのか

個人でReact Nativeを使ってアプリ開発をしている中で、他のアプリからファイルを受け取る必要があり、今回実装してみました。

正確になんて言うのか、言葉はわからないのですが……これです👇

共有シートから、特定のアプリでファイルを開くヤツ。
メールやSafari、AirDropで共有されたファイルも開けるようになります。

……なんて言うのが正しいの?

実装環境

  • React Native 0.63.4
  • XCode 12.3

なおRNプロジェクトの作成方法は割愛します。

📲 外部アプリからファイルを取得する

1. 受け取るファイルの種類を設定する

初っ端からXcodeを開きます。
Projectを開いたら、Target < [APP名] < Info < Document Typesを開いて、アコーディオン内の+ボタンを押してください。
image.png

ここに、受け取るファイルの種類を定義していきます。
image.png

  • Nameは適当に、ファイル形式の種類の名前を入れます。

  • Typeには、予め決められているMIMEのようなものを入れます。正しくは Uniform Type Identifier (UTI) と言うらしいです。
    必要な値はここから探せます。その他、独自形式も定義できるみたいです。

  • Handler Rank は指定した形式のファイルに対して、閲覧者なのか作成者なのかを宣言して、他のアプリとのランク付けをするもの、らしい、です。

意味
Owner 指定した種類のファイルを作成します
Default 指定した種類のファイルを開きます
Alternate 指定した種類のファイルのセカンダリビューアー
None 指定した種類のファイルに対して何もしない

2. 受け取ったファイルをRNに送るためのコードを書く

次に、受け取ったファイル(正しくはファイルのパス)を、React NativeのJavaScript側に送るためのコードを書きます。
コードを書くファイルは、AppDelegate.mです。
image.png

追加するコードは、(大分わかりにくいですが……)以下の緑色のコードです。
上の方でReact/RCTLinkingManager.hをインポートして、あとは下の方で3行だけコードを入れれば、ひとまず完成です。

AppDelegate.m
#import "AppDelegate.h"

#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
+ #import <React/RCTLinkingManager.h>

~~~中略~~~

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
   ~~中略~~
}

+ - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
+  return [RCTLinkingManager application:application openURL:url options:options];
+ }

3. JS側でファイルパスを受け取る

残るは、ReactNative側でファイルパスを受け取るだけです。
コードから予想できた方も多いと思いますが、URL Schemeで使うLinkingを使ってパスを取得します。
というか2と3に関しては、ドキュメントそのままです。

const handler = (filePath: string) => {
    // 処理内容
};

useEffect(() => {
  // アプリ起動前に呼ばれる
  Linking.getInitialURL().then((filePath) => {
    if (filePath) handler(filePath);
  });

 // アプリ起動後に呼ばれる
  Linking.addEventListener('url', (event) => handler(event.url));
}, []);

URL Schemeで取得するURLの多くは、 https://~~mailto:~~だと思いますが、ここで取得されるパスは file:///~~~になっていて、iOS内部の絶対パスが渡ってきます。

4. ファイルパスを使って処理する

ここに関しては、必要に応じて処理をしてください。
私はreact-native-fsを導入してreadFileで読み込みました。

🤔 LSSupportsOpeningDocumentsInPlaceと共存させる方法

iOS11から追加されたファイル.appから、各アプリのディレクトリにアクセスできるヤツ。

これの有効に必要なLSSupportsOpeningDocumentsInPlaceをオンにしていると、上記の方法で取得したファイルパスを使っても、No such file or directoryと怒られてしまう場合があります。

LSSupportsOpeningDocumentsInPlaceを有効にすると、ファイルをコピーせず直接オリジナルのファイルを参照しにいきます。
iOSはサンドボックスの制限が厳しいので、(正確な理由はわからんですが)おそらくJS側からオリジナルのファイルがあるパスにアクセスする権限がないのが原因かなと。

回避策として、tmpディレクトリにファイルをコピーしてしまう事にしました。

AppDelegate.m

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
  NSString* urlPath = url.path;
  if(![[NSFileManager defaultManager] isReadableFileAtPath:urlPath])
  {
    if([url startAccessingSecurityScopedResource])
    {
      NSString* destPath = [NSString stringWithFormat:@"%@/%@", NSTemporaryDirectory(), [url.path lastPathComponent]];
      if(![[NSFileManager defaultManager] copyItemAtPath:urlPath toPath:destPath error:nil]) {
        [[NSFileManager defaultManager] removeItemAtPath:destPath error:nil];
        [[NSFileManager defaultManager] copyItemAtPath:urlPath toPath:destPath error:nil];
      }
      [url stopAccessingSecurityScopedResource];
      NSURL* destURL = [NSURL fileURLWithPath:destPath];
      return [RCTLinkingManager application:application openURL:destURL options:options];
    }
  }
  return [RCTLinkingManager application:application openURL:url options:options];
}

読み取り権限がないファイルパスだった場合のみ、アプリのtempDirにファイルをコピーして、RNにコピーしたファイルのパスを渡すように変更しました。
これで、JS側からもファイルを読みに行くことができました🎉

普段Web言語ばかり扱っているので、こうしてネイティブ部分のコードに触れて理解できるようになると楽しくなってきますね。
時間を作って、Obj-CやSwiftもしっかり勉強したいと思いました。

なおネイティブアプリをまともに書いた経験が無いので、間違いなどあればご指摘ください🙇‍♂️

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?