FlutterのShareExtensionがわかる!実に!わかるぞ!最高に「ハイ!」ってやつだアアアアアアハハハハハハハハハーッ
[1]
わかる!実に!わかるぞ!最高に「ハイ!」ってやつだアアアアアアハハハハハハハハハハーッというわけで、お久しぶりです、わたなおです。今回はですね、FlutterなのにiOS側のみでShareExtension
を使用したいという狂気じみたことをやったので備忘録を書こうと思います。
あれ?前回の記事含め、Flutterである意味って、、、いや、気にしないことにしましょう。
お目汚しいたします、愚痴です
ShareExtension
とかiOSでしかできない機能の資料、少なすぎません??
タスクが終わったらタイトルの状態ですよ、、、
ShareExtension
とは、、、
ShareExtension
とは、⬇下の画像の黄色で囲まれた部分を押すことで表示されるUIで呼び出される処理とそのUIのことです。
よくエアドロで使うあの機能で写真アプリなどの列に自分のアプリを表示し、データを登録することが目標です。
自作したくない自分が色々動いた結果
Flutterにライブラリはあるようなのですが、説明が不十分だったのかiOSの変更などに追従できていなかったことで機能が実装できなかったので自作することにしました。
機能の流れ
- 共有データ(画像や音声)を選択し、共有用画面を開く
- アプリのアイコンを選択
ここからあとの実装はShare Extensionがどういう機能であるのかに依存します。
実装準備
脳内でキューピー3分クッキング👶の曲を流しながら、以下の手順でShareExtension
を準備しましょう。(慣れれば30分以内でできます。10回ループするだけですね。)
- Flutterで作成された、自作プロジェクトの
ios/Runner.xcworkspace
を起動します。名前が似ているのがありますが、白いアイコンの方を選んでください。 - Xcodeのナビゲーターエリアの一番上にある
Runner
(ディレクトリではないRunner
を選んでください)をクリックします。 - 右の方にある
Project
やTargets
の下にある+
をクリックし、下のようなウィンドウが出てきます。そのフィルターにShare Extension
と入力し、先ほどのお馴染みのアイコンを選択します。
- Swiftのプロジェクト作成のようなウィンドウが見えると思いますので、
ShareExtension
と入力してください。以降の内容を揃えたいので同名でお願いします。
以上を実施後、自作プロジェクトのios
配下にShareExtension
、Xcodeのナビゲーターエリアに青色ディレクトリのアイコンでShareExtension
とあるはずです。これで、表示自体はされるはずなので試してみましょう。
見えましたね。これで実装の準備は完了です。
できること・できないこと
自分が調査した範囲でできることとできないことをまとめようと思います。不十分な可能性もありますが、その場合はコメントしていただけると助かります。
できること
-
ShareExtension
を起動した要因を取得する(画像や音声など) - UserDefaultなどのiOS依存の機能を使用すること
- デフォルトのUIの見た目を変更すること
- 独自の見た目を実装すること(
ShareViewController
が継承するクラスをUIViewController
に変えます) - 元アプリとデータなどを共有すること
- PODでライブラリを追加する
できないこと
- 元となったアプリを起動すること(これ、やりたかった、、、 😭)
外部ライブラリの追加方法
今回、Flutterを使用しているため、Podfile
に下記のように記載します。今回はAPI呼び出し時にTokenが欲しいのでFirebase/Core
とFirebase/Auth
を使用できるようにします。
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
target 'Share_Extension' do
use_frameworks!
pod 'Firebase/Core'
pod 'Firebase/Auth'
pod 'GoogleUtilities/AppDelegateSwizzler'
pod 'GoogleUtilities/MethodSwizzler'
pod 'GoogleUtilities/Network'
pod 'GoogleUtilities/NSData+zlib'
pod 'GoogleUtilities/Environment'
pod 'GoogleUtilities/UserDefaults'
pod 'GoogleUtilities/Reachability'
pod 'GTMSessionFetcher'
end
今回はFlutterなのでPodfileですが、SPMでのやり方は考えたくないので書かないことにします。(後日できたら…)
詳細な技術の話
準備
元からあるUIKit + Storyboard
を使用します。UIViewControllerRepresentable
を使えばSwiftUI
も使えるかもしれないですが、そこまでする義理もないのでこれで行きます。
class ShareViewController: UIViewController {
@IBOutlet weak var progressLoader: UIProgressView!
@IBOutlet weak var loadingLabel: UILabel!
}
これで読み込み機能時の演出を作ります。チープでもいいでしょ。許せ、、、
Firebaseの初期化とアプリ本体とログイン状況の共有
この処理は必須級の関数です。リンク先のFirebaseのページを元に適切なKeyChainKey
を設定してください。
func initFirebase() {
FirebaseApp.configure()
do {
// KeyChainでアプリ本体とFirebase Authを共有
try Auth.auth().useUserAccessGroup("KeyChainKey")
} catch {
// この処理の詳細は後述します orz
closeView()
}
}
この関数を好きなタイミングで読んで欲しいのとFirebaseの処理を使う前までに必ず読み込まれて欲しいです。が、AppDelegate
をここでは使用できないのでloadView()
などで呼び出すことをおすすめします。
API呼び出し部分と最重要処理
この処理callApis()
が呼び出される前に必ずFirebaseApp.configure()
が呼び出されている必要があります。上の内容ができていれば大丈夫でしょうが一応、注意してください。
/// どっかで呼ばれてるself.progressLoader.setProgress(0, animated: true)
func closeView() {
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
func callApis() {
Task {
defer {
closeView()
}
do {
guard let jwt = try? await Auth.auth().currentUser?.getIDToken() else {
throw """
ログインしていません。
アプリでログイン後、再度、実行してください。
"""
}
DispatchQueue.main.async {
self.loadingLabel.text = "次の情報の取得中..."
self.progressLoader.setProgress(1/n, animated: true)
}
self.extensionContext!.completeRequest(returningItems, completionHandler)
とは
最重要処理1つ目この処理を呼び出すことで自動でShareExtension
の画面を閉じることができます。手で閉じることもできますが、UXを損ねそうなので適切なタイミングでこれを呼び出すようにします。Concurrency
じゃないというのはびっくりしました。
DispatchQueue.main.async {}
とは
最重要処理2つ目SwiftのUIKitではUIの更新のタイミングがメインのスレッドでないといけないのでメインスレッドに処理を入れ込むためにこれを書いています。まぁ、有名ですね。
defer
をしている理由
実際の処理ではメッセージを表示して4秒後ぐらいで画面を閉じたいので、これを記載しています。各箇所で呼ぶより楽なのでこうしているってだけです。defer
の中身は省略してます。
まとめ
Flutter(iOS)でShareExtension
を使用する方法は、普通にSwiftでShareExtension
を入れてCocoaPods
でライブラリを入れる感じで大丈夫でした。もし、よろしければShareExtension
を使ってみるのはいかがでしょうか?
Flutterでは、2度とやらん、、、
-
アニメ「ジョジョの奇妙な冒険第3部スターダスト・クルセイダース」第48話 ↩︎
Discussion