SwiftUIでShare Extensionを実装する

## はじめに
いきなりタイトルと矛盾するようですが、残念ながら現時点（2022年4月）ではShare Extensionを純SwiftUIで実装することはできません。多少UIKitが必要になります。

Share Extensionについて詳しく知る
[App Extension Programming Guide: Creating an App Extension](https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionCreation.html#//apple_ref/doc/uid/TP40014214-CH5-SW1)

## （手順1）Share ExtensionをTargetに追加
File > New > Target... からShare Extensionを選択。
![スクリーンショット 2022-04-28 23.51.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/396300/229dc478-ecf7-729c-8ed6-949145af55bf.png)
すると以下のファイルが生成されます。
#### MainInterface.storyboard
テキストやURLなどが共有されるとまずこれが開かれます。エントリーポイントとしてShareViewControllerが指定されています。

#### ShareViewController.swift
エントリーポイントのビューコントローラーには、extensionContextというプロパティに[NSExtensionContext](https://developer.apple.com/documentation/foundation/nsextensioncontext)のオブジェクトが当てられます。これが共有の中心人物で、共有されたデータを取り出したり、共有が完了して画面を閉じたりするのはこのオブジェクトを通して行います。
この段階ではSLComposeServiceViewControllerというビューコントローラーが継承されていますが、これを使う必要は全然ありません。extensionContextを扱えるビューコントローラーでさえあればいいので、通常はカスタムのビューコントローラーを使います。

#### Info.plist
受け入れるデータの形式（URLだけ受け入れる、など）を指定したりします。詳細は[こちらの記事](https://qiita.com/KosukeQiita/items/994693da551a7101cc9c#3-infoplist設定)でわかりやすく解説されています。


## （手順2）以下のコードをコピペ
```swift:ShareView.swift
import SwiftUI

struct ShareView: View {
    @ObservedObject var model = ShareModel()
    
    var body: some View {
        NavigationView {
            Form {
                TextField("Shared Text", text: self.$model.sharedText)
            }
            .onSubmit {
                self.model.submit()
            }
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button {
                        self.model.cancel()
                    } label: {
                        Text("Cancel")
                    }
                }
            }
        }
    }
}
```
```swift:ShareModel.swift
import SwiftUI
import UniformTypeIdentifiers

class ShareModel: ObservableObject {
    @Published var sharedText: String = ""
    var extensionContext: NSExtensionContext?
    
    init() {
        // 初期化処理
    }
    
    func configure(context: NSExtensionContext?) {
        self.extensionContext = context
        
        guard let item = context?.inputItems.first as? NSExtensionItem else { return }
        guard let itemProvider = item.attachments?.first else { return }
        
        // テキストが共有された場合
        if itemProvider.hasItemConformingToTypeIdentifier(UTType.text.identifier) {
            // テキストを取り出して代入
            itemProvider.loadItem(forTypeIdentifier: UTType.text.identifier, options: nil) { data, error in
                guard let sharedText = data as? String else { return }
                DispatchQueue.main.async {
                    self.sharedText = sharedText
                }
            }
        }
    }
    
    func cancel() {
        self.extensionContext?.cancelRequest(withError: ShareError.cancel) // 共有をキャンセル
    }
    
    func submit() {
        // ここに共有処理を記述する
        
        self.extensionContext?.completeRequest(returningItems: nil) // 共有完了
    }
    
    enum ShareError: Error {
        case cancel
    }
}
```
バックエンドとの通信などの共有処理は省略しています。バックエンドを用いないアプリの場合はAppGroupを使ったりして上手くやります。


## （手順3）ShareViewControllerを変更
SwiftUIのビューをUIKitで利用するために[UIHostingController](https://developer.apple.com/documentation/swiftui/uihostingcontroller)というクラスを使います。
```swift:ShareViewController.swift
import SwiftUI

class ShareViewController: UIHostingController<ShareView> {
    
    required init?(coder: NSCoder) {
        super.init(coder: coder, rootView: ShareView())
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.rootView.model.configure(context: self.extensionContext)
    }
}
```
ShareViewContoroller.extensionContextをShareModelに渡し、ShareModelで共有の処理を行えるようにします。

ここまでの手順をたどれば、このようにテキストを共有することができるはずです。
![スクリーンショット 2022-04-29 2.47.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/396300/6be1dfce-412b-24d9-e660-46d81ffa39fa.png)


## 単語帳アプリを作りました！
自分で単語を追加する系の単語帳アプリです。

Safariなどの外部のアプリから単語を追加するために本アプリでもShare Extensionを実装しています。[Vocabula](https://apps.apple.com/jp/app/vocabula-%E8%87%AA%E5%88%86%E3%81%A7%E4%BD%9C%E3%82%8B%E5%8D%98%E8%AA%9E%E5%B8%B3/id1616390206)