【Swift】Share Extensionでデータを取得・操作する方法!

この記事からわかること
- Swiftで共有メニューに表示させる方法
- Share Extensionの使い方
- 送信されたデータを取得する方法
- 画像やテキスト、URLを取得する
- SLComposeServiceViewControllerクラスの役割
- 共有シートのデザインを変更するには?
index
[open]
\ アプリをリリースしました /
環境
- Xcode:15.0.1
- iOS:17.0
- Swift:5.9
- macOS:Sonoma 14.1
この記事は以下の記事の続きです。Share Extensionの導入方法や基盤の作成方法を知りたい方は以下の記事を参考にしてください。
SLComposeServiceViewController継承クラス
Share Extensionのターゲットを追加するとSLComposeServiceViewController
を継承したShareViewController
クラスが自動生成されます。
import UIKit
import Social
class ShareViewController: SLComposeServiceViewController {
override func isContentValid() -> Bool {
// Do validation of contentText and/or NSExtensionContext attachments here
return true
}
override func didSelectPost() {
// This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
// Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
override func configurationItems() -> [Any]! {
// To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
return []
}
}
それぞれのメソッドの役割は以下のとおりです。
- isContentValid:投稿ボタンのバリデーションロジック
- didSelectPost:投稿ボタン押下後の処理
- configurationItems:共有シート下部のリスト管理
isContentValid:投稿ボタンのバリデーションロジック
isContentValid
メソッドでは投稿ボタンがどの条件の時にアクティブでどの条件の時に非アクティブにするかを設定することができます。例えば共有テキストが0文字の場合は非アクティブにする場合self.contentText
で入力文字に参照できるのでそのcount
が0ならfalse
を返すように実装します。
override func isContentValid() -> Bool {
// 入力文字が0文字の場合は投稿ボタンを非アクティブにする
if let contentText = self.contentText, contentText.count > 0 {
return true
} else {
return false
}
}
didSelectPost:投稿ボタン押下後の処理
didSelectPost
メソッドでは投稿ボタンを押下した後の処理を行うことができます。まずは投稿されてきたテキストを取得してみます。テキストを取得するには先ほど同様にself.contentText
を参照します。最後に実行するcompleteRequest
メソッドは投稿が成功したことをシステムに通知する役割を持っています。
override func didSelectPost() {
if let contentText = self.contentText {
print("投稿されたテキスト: \(contentText)")
}
// 投稿が成功したことをシステムに通知する
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
URLを取得する
URLを取得するには少し複雑になってきます。投稿された情報はextensionContext
プロパティの中にあるので対象のデータを掘り進めて取得する必要があります。詳細なコードの役割はコメントに記述しておきました。
override func didSelectPost() {
// 拡張コンテキストからinputItems:[NSExtensionItem]内にある最初の入力アイテムを取得
guard let extensionItem = self.extensionContext?.inputItems.first as? NSExtensionItem else { return }
// 入力アイテムの中からattachments:[NSItemProvider]内にある最初のアタッチメントを取得
guard let itemProvider = extensionItem.attachments?.first as? NSItemProvider else { return }
// 「public.url」タイプのデータがあるかどうか確認
if itemProvider.hasItemConformingToTypeIdentifier("public.url") {
// 非同期でURLを読み込む
itemProvider.loadItem(forTypeIdentifier: "public.url", options: nil, completionHandler: { (url, error) in
// 成功すればURLを取得できる
if let url = url as? URL {
print("投稿されたURL: \(url.absoluteString)")
// 成功通知
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
})
}
}
公式リファレンス:hasItemConformingToTypeIdentifier(_:)メソッド
hasItemConformingToTypeIdentifier
メソッドの引数にはタイプ識別子(Uniform Type Identifiers)を渡す必要があります。URLの場合はpublic.url
ですが、UniformTypeIdentifiers
をimportすることで定数UTType.url.identifier
が使えるようになります。
import UniformTypeIdentifiers
公式リファレンス:UniformTypeIdentifiers
if itemProvider.hasItemConformingToTypeIdentifier(UTType.url.identifier) {
// 非同期でURLを読み込む
itemProvider.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil, completionHandler: { (url, error) in
// 成功すればURLを取得できる
if let url = url as? URL {
print("投稿されたURL: \(url.absoluteString)")
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
})
}
画像(Image)を保存する
URLの画像を取得するにはloadPreviewImage
メソッドを使用して以下のように実装します。
// 「public.url」タイプのデータがあるかどうか確認
if itemProvider.hasItemConformingToTypeIdentifier(UTType.url.identifier) {
itemProvider.loadPreviewImage(options: nil, completionHandler: { (item, error) in
// 画像を取得する
if let image = item as? UIImage {
print("投稿された画像のサイズ: \(image.size)")
}
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
})
}
画像単体の場合は以下ですかね。(未検証)
if itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
// 画像データを非同期で読み込む
itemProvider.loadItem(forTypeIdentifier: UTType.image.identifier, options: nil, completionHandler: { (imageData, error) in
if let imageData = imageData as? Data {
// imageDataをUIImageに変換
if let image = UIImage(data: imageData) {
// ここで取得した画像を使用できます
print("投稿された画像のサイズ: \(image.size)")
}
}
})
}
データを保存してアプリ側で取得する
投稿されたデータをiOSアプリ側で受け取るためにはApp Groupsを使用してUserDefaultsに保存して、データを渡す必要があります。
おすすめ記事:【Swift UI】App GroupsでWidgetやアプリ間でデータを共有する方法!
override func didSelectPost() {
guard let contentText = self.contentText else { return }
print("投稿されたテキスト: \(contentText)")
// App Groupsを利用したUserDefaultsへ保存
let suiteName = "group.com.XXXXXXXX" // 指定したグループID
guard let appGroupDefaults = UserDefaults(suiteName: suiteName) else { return }
appGroupDefaults.set(contentText, forKey: "SHARE_EXTENSION")
// 投稿が成功したことをシステムに通知する
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
アプリ側ではUserDefaultsからデータを取り出すだけです。
private func getShareTextData() {
let suiteName = "group.com.XXXXXXXX"
guard let appGroupDefaults = UserDefaults(suiteName: suiteName) else { return }
let text = appGroupDefaults.object(forKey: "SHARE_EXTENSION")
print(text)
}
configurationItems:共有シート下部のリスト管理
configurationItems
メソッド内では表示される共有シートの下部に追加でリストなどを追加し、アクションを追加することができます。例えば以下のようにテキストや画像を選択させる行を追加することができます。

var textItem: SLComposeSheetConfigurationItem?
override func configurationItems() -> [Any]! {
var items: [Any] = []
// テキスト入力アイテム
textItem = SLComposeSheetConfigurationItem()
textItem?.title = "テキスト"
textItem?.value = "初期のテキスト"
textItem?.tapHandler = { [weak self] in
guard let self = self else { return }
let alertController = UIAlertController(title: "テキストを入力", message: nil, preferredStyle: .alert)
alertController.addTextField { textField in
textField.placeholder = "ここにテキストを入力"
}
let okAction = UIAlertAction(title: "OK", style: .default) { _ in
if let textField = alertController.textFields?.first, let inputText = textField.text {
self.textItem?.value = inputText
// 画面を更新
// self.reloadConfigurationItems()
self.reloadInputViews()
}
}
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
}
items.append(textItem!)
// 画像選択アイテム
let imageItem = SLComposeSheetConfigurationItem()
imageItem?.title = "画像"
imageItem?.value = "画像を選択"
imageItem?.tapHandler = { [weak self] in
guard let self = self else { return }
let imagePickerController = UIImagePickerController()
imagePickerController.sourceType = .photoLibrary
imagePickerController.delegate = self
self.present(imagePickerController, animated: true, completion: nil)
}
items.append(imageItem!)
return items
}
UIImagePickerController
の使い方に関しては以下の記事を参考にしてください。
共有シートのデザインをカスタマイズする
共有シートのデザインはある程度であればカスタマイズすることが可能です。しかしタイトルの色を変更する方法が見つけられませんでした。

override func viewDidLoad() {
super.viewDidLoad()
// タイトルを変更
self.title = "タイトル"
guard let navigationController = self.navigationController else { return }
// 上部ボタンの色
navigationController.navigationBar.tintColor = .white
// 上部背景色
navigationController.navigationBar.backgroundColor = .cyan
// 上部ボタンの文言
guard let controller = navigationController.viewControllers.first else { return }
controller.navigationItem.rightBarButtonItem!.title = "GO!"
controller.navigationItem.leftBarButtonItem!.title = "Back"
}
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。