【SwiftUI】UIActivityViewController・URLスキームを用いたシェア機能の実装

# はじめに
SwiftUIにおけるシェア機能の実装方法について学習したので、備忘録として残します。
シェア機能の実装方法はいくつかありますが、今回は`UIActivityViewController`と`URLスキーム`の2つを学習したため、これらの実装方法をまとめます。

---
## 環境
【Xcode】16.2
【iOS】18.2
【macOS】Sequoia 15.3.1

---

## ポイント
・`UIActivityViewController`は最も簡単で一般的なシェア機能。
　XやLINEなど、ユーザーがインストールしているさまざまなアプリへ共有できる。
 ・`URLスキーム`は特定のアプリに直接シェアする方法。
　あらかじめシェアして欲しいアプリが決まっている場合にこちらを使用。
 
---

## 1.`UIActivityViewController`と`URLスキーム`について
まずはそれぞれの違いとメリット・デメリットについてまとめます。

| 方法 | 違い | メリット | デメリット |
|------|------|---------|-----------|
| **UIActivityViewController**<br> | ユーザーがインストールしている様々なアプリにシェアできる | ・`UIActivityViewController`を使うだけで簡単に実装できる<br>・システム標準のUIなのでUXが統一されている | ・シェアボタンのデザインやシェア対象のカスタマイズが難しい |
| **URLスキーム**<br> | 特定のアプリ（XやLINEなど）を指定してシェアできる | ・どのアプリでシェアするかを固定できる<br>・シェア内容を細かくカスタマイズできる | ・対象のアプリがインストールされていないと動作しない |


基本的には簡単で一般的なUIActivityViewControllerを使用し、特定のアプリを指定してシェアさせたい場合にURLスキームを使用するとよさそう。

---

## 2.実装方法
続いて、各機能の実装方法についてまとめます。

## `UIActivityViewController`

### 📌 UIActivityViewControllerを使う流れ
1. シェアしたい内容（テキストやURL、画像など）を準備する  
2. `UIActivityViewController` を作成し、シェア内容を渡す  
3. `.sheet()` を使ってシェア画面を表示する

---

```swift
import SwiftUI

struct ContentView: View {
    @State private var isShowingShareSheet = false
    
    var body: some View {
        VStack {
            Text("シェア機能\n(UIActivityViewController)")
                .font(.title)
                .padding()
            Button {
                isShowingShareSheet = true
            } label: {
                Text("シェアする")
                    .font(.title2)
                    .padding()
                    .foregroundStyle(.white)
                    .background(Color.blue)
                    .cornerRadius(10)
            }
            .padding()
            .font(.title)
            .sheet(isPresented: $isShowingShareSheet) {
                ShareSheet(activityItems: ["このアプリを試してみよう！ #シェア機能"])
            }
        }
    }
}

// ⬇️ UIViewControllerRepresentable でSwiftUIでも使えるようにする
struct ShareSheet: UIViewControllerRepresentable {
    let activityItems: [Any]
// ⬇️ UIKitの UIActivityViewController をSwiftUIのViewとして作成する
    func makeUIViewController(context: Context) -> some UIViewController {
        UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
    }
    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
    }
}
```
まず、`UIActivityViewController`はUIKitのコンポーネントであるため、そのままではSwiftUIで使用することはできません。
SwiftUIのViewとして使うには`UIViewControllerRepresentable`を用います。

`let activityItems: [Any]`でシェアする内容を入れる定数を用意します。 文字列や画像、URLなどに対応するため型はAny、複数のアイテムを同時にシェアできる仕様なので配列にします。

続いて`func makeUIViewController`で、UIKitの`UIActivityViewController`をSwiftUIのViewとして作成します。
`Context`は、SwiftUIとUIKitの間で情報をやり取りするためのオブジェクトです。（今回は特に使ってません）

`some UIViewController` は、「戻り値の正確な型を明示しなくても、`UIViewController` の仲間を返すよ！」という意味です。  
例えば、後から `UIActivityViewController` 以外の `UIViewController` に変更したくなった場合でも、柔軟に対応できます。

`let activityItems: [Any]`で定義したシェアしたい内容（`activityItems`）をUIActivityViewController に渡して、シェアできるようにします。

`applicationActivities: nil`とすることで、デフォルトのシェアオプション（LINE, X, メールなど）が表示されます。
カスタムのアクティビティを追加する場合は、`applicationActivities`に独自の`UIActivity`を渡します。

`func updateUIViewController`は、SwiftUIの状態が変わったときに **UIKit側の`UIActivityViewController` を更新するためのメソッド** です。  
今回はシェア機能において特に更新が不要なため、空のままにしています。
    
---

## `URLスキーム`

### 📌 URLスキームを使う流れ
1. シェアしたいアプリのURLスキームを調べる
2. `UIApplication.shared.open()` を使ってURLスキームを開く
3. アプリがインストールされているか確認し、未インストール時の対策をする（例: `canOpenURL`）

---


```swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("シェア機能（URLスキーム）")
                .font(.title)
                .padding()
            
            Button {
                shareOnX()
            } label: {
                Text("Xでシェア")
                    .font(.title2)
                    .padding()
                    .foregroundStyle(.white)
                    .background(Color.blue)
                    .cornerRadius(10)
            }
        }
    }
}

//URLスキームを使ってXでシェア
func shareOnX() {
    let text = "このアプリを試してみよう！ #シェア機能"
// シェア用のテキストをエンコード
    guard let encodedText = text.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
        return  // エンコードに失敗した場合は処理を終了
    }
    
    // Xアプリ用のURLスキーム
    if let url = URL(string: "twitter://post?message=\(encodedText)"),
       UIApplication.shared.canOpenURL(url) {
        UIApplication.shared.open(url, options: [:], completionHandler: nil)
        return
    }
    
    // Xアプリがインストールされていない場合はWebで開く
    if let webURL = URL(string: "https://twitter.com/intent/tweet?text=\(encodedText)") {
        UIApplication.shared.open(webURL, options: [:], completionHandler: nil)
    }
}
```
※上記はXでのシェアの例です。

今回は`"このアプリを試してみよう！ #シェア機能"`というテキストをXでシェアさせたいです。
ただ、日本語やスペースなどの特殊文字が含まれているとURLとして正しく処理されないため、`addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)` を使ってURLエンコード（%エンコーディング）を行っています。

続いて、URLエンコードしたテキストを、Xのシェア用URLに組み込みます。
Xアプリを開いて、特定のテキストを投稿するURLスキームは以下の通りです。
```swift
twitter://post?message=投稿したいテキスト
```

`UIApplication.shared.canOpenURL(url)`で指定したurlを開けるかどうかを事前に確認し、Xアプリがインストールされていればアプリを開き、インストールされていなければWebでURLを開くようにします。
Webの共有URLは以下のとおりです。
```swift
https://twitter.com/intent/tweet?text=投稿したいテキスト
```

`options: [:]` は`UIApplication.shared.open`メソッドのoptionsパラメータに空の辞書 [:] を渡す処理で、デフォルトの動作となります。 このパラメータは、特定のオプションを指定するために使用されますが、特に指定がない場合は空の辞書を渡す形で問題ないです。

今回は例としてXで共有する形で記載しましたが、それ以外でも各アプリのURLスキームに組み込むことでシェアが可能になります。
例として、以下はLINEを開いてメッセージを送信するURLスキーム、Webの共有URLです。
```swift
line://msg/text/送信したいテキスト
```
```swift
https://line.me/R/msg/text/?送信したいテキスト
```
# おわりに
自分の知らなかったことや理解が難しかった内容を詰め込んで記載したので、読みづらい文章になっていたらすみません。。。
また、間違っている部分などあればご指摘ただけますと幸いです。