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

この記事からわかること

  • Swift共有メニュー表示させる方法
  • Share Extension使い方
  • 送信されたデータ取得する方法
  • 画像テキストURLを取得する
  • SLComposeServiceViewControllerクラス役割
  • 共有シートデザイン変更するには?

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

この記事は以下の記事の続きです。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:投稿ボタンのバリデーションロジック

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メソッド内では表示される共有シートの下部に追加でリストなどを追加し、アクションを追加することができます。例えば以下のようにテキストや画像を選択させる行を追加することができます。

【Swift】Share Extensionでデータを取得・操作する方法!
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の使い方に関しては以下の記事を参考にしてください。

共有シートのデザインをカスタマイズする

共有シートのデザインはある程度であればカスタマイズすることが可能です。しかしタイトルの色を変更する方法が見つけられませんでした。

【Swift】Share Extensionでデータを取得・操作する方法!
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"
}

Share Extensionでデータを共有する

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index