[Flutter] ブラウザアプリからShareExtensionでアプリを立ち上げてURLを共有する方法(iOS)

  • 2025年4月14日
  • 2025年4月21日
  • Flutter
  • 11View
  • 0件

iOSアプリで他のアプリからの共有機能を実装したいと考えたことはありませんか?
特に、ブラウザアプリ(Safariなど)から自作のFlutterアプリへURLを共有したい場面は多いはずです。
本記事では、Share Extensionを使って、iOS上のブラウザからFlutterアプリを起動し、共有されたURLを受け取る方法について、ステップバイステップで解説します。

1. パッケージのインストール

次のパッケージをpubspec.yamlに記載するか、ターミナルからインストールする。
・receive_sharing_intent
・app_links

dependencies:
  flutter:
    sdk: flutter
  receive_sharing_intent: ^1.8.1
  app_links: ^6.4.0

2. Share Extensionを作成する

Xcodeを立ち上げ、ツールバーから File > New > Target を選択する。

[Share Extension] を選択する

[Product Name] を入力する

次のダイアログが表示されたら [Activate] をクリック

ios/Share Extension/Info.plist を以下の内容にする

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>AppGroupId</key>
  <string>$(CUSTOM_GROUP_ID)</string>
  <key>CFBundleVersion</key>
  <string>$(FLUTTER_BUILD_NUMBER)</string>
  <key>NSExtension</key>
  <dict>
      <key>NSExtensionAttributes</key>
      <dict>
          <key>NSExtensionActivationRule</key>
          <dict>
              <key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
              <integer>1</integer>
          </dict>
      </dict>
      <key>NSExtensionMainStoryboard</key>
      <string>MainInterface</string>
      <key>NSExtensionPointIdentifier</key>
      <string>com.apple.share-services</string>
  </dict>
</dict>
</plist>

ios/Podfile を下記の通り修正する

target 'Runner' do
  use_frameworks!
  use_modular_headers!

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
  target 'Share Extension' do // ←この内容に修正
    inherit! :search_paths
  end
end

3. App Groupsを設定する

Runner > Signing & Capabilities の [+ Capability] をクリック

表示されたダイアログの [App Groups] をクリック

追加されたApp Groupsに [“group.” + Bundle Identifier] を入力する

Share Extension も同様に App Groups を追加する

ios/Runner/Runner.entitlements に下記内容を追加する
ios/Share Extension/ShareExtension.entitlements にも同様に追加する

<key>com.apple.security.application-groups</key>
<array>
	<string>group.xxx.yyy.zzz</string> // ←App Groupsに入力した値を設定する
</array>

ios/Runner/Info.plist に下記内容を追加する

<key>AppGroupId</key>
<string>$(CUSTOM_GROUP_ID)</string>
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>xxx</string> // ←任意の値(appを起動するURLとなるためapp名が好ましい)
        </array>
    </dict>
</array>

Runner > Build Settings の [+] をクリックし、下記のデータを追加する
・key・・・CUSTOM_GROUP_ID
・value・・App Groupsに入力した値

Share Extension も同様に追加する

Runner > Build Phases の [Embed Foundation Extension] を [Thin Binary] の上に移動させる

4. Host app から渡された URL を取得し、メインアプリ を立ち上げる処理を実装する

ios/Share Extension/ShareViewController.swift を以下の内容に書き換える

import SwiftUI
import UniformTypeIdentifiers
 
class ShareViewController: UIHostingController<ShareView> {

    // キャンセル時のエラー定義
    enum ShareError: Error {
        case cancel
    }
 
    required init?(coder: NSCoder) {
        super.init(coder: coder, rootView: ShareView())
    }
 
    // ビューがロードされた時の処理
    override func viewDidLoad() {
        super.viewDidLoad()
        Task {
            _ = await openAppWithUrl() // 非同期でURL処理を実行
        }
    }
 
    // ビューが表示された時の処理
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        extensionContext?.completeRequest(returningItems: nil)
    }
 
    private func openAppWithUrl() async -> Bool {
        // 1. 共有されたアイテムの取得
        guard let item = extensionContext?.inputItems.first as? NSExtensionItem,
              let itemProvider = item.attachments?.first else { return false }
        // 2. 共有アイテムがURLタイプかチェック
        guard itemProvider.hasItemConformingToTypeIdentifier(UTType.url.identifier) else { return false }
        do {
            // 3. URLデータの読み込み
            let data = try await itemProvider.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil)
            // 4. URLの処理とアプリ起動
            guard let url = data as? NSURL,
                  // URLをエンコード(特殊文字を安全に扱えるように)
                  let encodedUrl = url.absoluteString?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed),
                  // カスタムURLスキーム(xxx://)を使用してアプリ起動用URLを作成
                  let openAppUrl = URL(string: "xxx://\(encodedUrl)") ←xxxは、ios/Runner/Info.plistのCFBundleURLSchemesの値
            else { return false }
            // 5. メインアプリを開く
            return await UIApplication.sharedApplication().open(openAppUrl)
        } catch let error {
            print(error)
            return false
        }
    }
}

lib/main.dart に下記処理を実装(以下の例は、Host appから渡されたURLをWebViewControllerで開く処理です)

// アプリ起動時のURL処理
Future<void> _handleInitialUri() async {
  try {
    final uri = await _appLinks.getInitialLink();
    if (uri != null && uri.host.isNotEmpty) {
      final decodedUrl = Uri.decodeComponent(uri.host);
      controller.loadRequest(Uri.parse(decodedUrl)); // WebViewControllerでURLを開く
    }
  } catch (e) {
    print("初期URL取得エラー: $e");
  }
}

// アプリ起動中のURL受信監視
void _listenToUriLinks() {
  _appLinks.uriLinkStream.listen((uri) {
    if (uri != null && uri.host.isNotEmpty) {
      final decodedUrl = Uri.decodeComponent(uri.host);
      controller.loadRequest(Uri.parse(decodedUrl)); // WebViewControllerでURLを開く
    }
  }, onError: (err) {
    print("リンク受信エラー: $err");
  });
}