1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【SwiftUI】ShareLinkで画像を共有する際の落とし穴

Last updated at Posted at 2024-09-26

目次

  • ShareLinkについて
  • ShareLinkに渡す画像の型
  • 「画像を保存」アクションを表示させる方法
  • おまけ(補足)
  • 再現可能なコード全部

ShareLinkについて

IMG_2124.jpeg

ShareLinkはiOS16以上で使用可能なSwiftUIのAPIです。いわゆる「共有シート」を簡単に表示することができます。UIKitのUIActivityViewControllerでは、表示や非表示をiPhone・iPadに応じて制御する必要があり少々手間がかかりますが、ShareLinkは1行で共有シートを簡単に表示できます。

今回はShareLinkにおける画像の共有に絞って解説します。

ShareLinkに渡す画像の型

1. URLによる共有

メモリ上の画像ではなく、ドキュメントディレクトリやアプリのプロジェクトに追加された画像など、ディスクに保存されている画像に対してはURLを渡すことで共有が可能です。

2. メモリ上の画像の共有

共有する画像がImage型やUIImage型などメモリ上にある場合の手順です。このケースは公式ドキュメントにはあまり詳しく記載されていません。

まず、前提としてShareLinkに渡すitem(共有する対象物)はTransferable型に準拠している必要があります。

Transferable ドキュメント

ここで注意すべき点は、UIImageTransferable型に準拠していないため、直接渡すことができません。XcodeでitemUIImageを渡すと以下のエラーが発生します。

Generic struct 'ShareLink' requires that 'UIImage' conform to 'Transferable'

一方で、SwiftUIのImage型はTransferable型に準拠しているため、そのまま画像を共有することができます。Image(uiImage:)としてUIImageを渡すことで、画像を簡単に共有することができます。

ただし、UIImageを変換せずとも共有する方法もあります。UIImage自体をTransferableに準拠させることが可能です。

Transferableに準拠させる拡張 (pngへの変換)
extension UIImage: Transferable {
    public static var transferRepresentation: some TransferRepresentation {
        DataRepresentation(exportedContentType: .png) { image in
            if let pngData = image.pngData() {
                return pngData
            } else {
                throw ConversionError.failedToConvertToPNG
            }
        }
    }
    enum ConversionError: Error {
        case failedToConvertToPNG
    }
}

この拡張により、UIImageがドラッグ&ドロップや共有など、転送時にはPNG形式に変換されるようになります。JPEGやHEIFへの変換も可能で、カスタマイズ性が高いです(Image型の場合、デフォルトでPNGに変換されます)。この方法により、UIImage型をそのままShareLinkに渡すことが可能です。

AppleのドキュメントではカスタムなTransferable型(Image + String)の定義が紹介されていますが、画像の共有だけであれば前述の方法が最も簡単です。

1. URLによる共有と2. メモリ上の画像の共有の違い

URLで共有する場合、ファイル名、ファイル形式、ファイルサイズが表示されるため、プレビューの設定は不要です。しかし、メモリ上の画像の場合はカスタムプレビューの設定が必要です。

1. URLによる共有 2. メモリ上の画像の共有
url.png メモリ.png

「画像を保存」アクションを表示させる方法

前述の方法により画像の共有は可能ですが、実機やシミュレータで確認すると、「画像を保存」アクションが表示されないことがあります。コピーやプリント、共有アルバムへの追加などのアクションは表示されますが、画像保存のアクションが欠けている場合があります。

多くのユーザーは、画像の共有ボタンを使ってSNSへの転送や端末への保存を期待しているため、この問題は重要です。これはURL型、Image型どちらのケースでも発生します。

解決方法

この問題は写真アプリへのアクセス許可が原因です。アプリのInfo.plistPrivacy - Photo Library Additions Usage Descriptionを追加することで、「画像を保存」アクションが表示されるようになります。
Info.plist Photo Library Add Usage Documentation
画像.jpeg
画像を保存アクション
画像を保存.jpeg

この設定で指定する文字列は、初めて「画像を保存」アクションを実行した際に表示されるアラート文言です。
アクセス管理.jpeg

カスタムのImagePickerなどを使用していて、既にPrivacy - Photo Library Usage Descriptionを追加している場合はこの問題は発生しないはずです。
Info.plist Photo Library Usage Documentation

最近では、許可が不要でプライベートアクセスが可能なPhotosPickerの利用が増えていますが、アクセス許可の設定を確認することが重要だと思います。

おまけ(補足)

補足として、SwiftUIのnavigationDocumentを紹介します。これもShareLinkと同様にTransferable型を渡すことで、インラインスタイルのnavigationTitleがボタンに変わり、共有オプションが表示されます。これを使うことでShareLinkと同等の機能を提供できます(ネイティブなファイルアプリに実装されています)。
Simulator Screenshot - iPhone 15 Pro - 2024-09-26 at 22.37.34.jpeg

再現可能なコード全文

TestView.swift
import SwiftUI

struct TestView: View 
    let imageFromURL = Bundle.main.url(forResource: "logo.png", withExtension: nil)!
    let imageFromAssets = Image("logo")
    let uiImageFromAssets = UIImage(imageLiteralResourceName: "logo")

    var body: some View {
        NavigationStack{
            VStack(spacing: 20) {
                    ShareLink(
                        "URLから共有",
                        item: imageFromURL
                    )
                    ShareLink(
                        "Assetsから共有(UIImage型)",
                        item: uiImageFromAssets,
                        preview: SharePreview(
                            "添付画像",
                            image: uiImageFromAssets
                        )
                    )
                    ShareLink(
                        "Assetsから共有(Image型)",
                        item: imageFromAssets,
                        preview: SharePreview(
                            "添付画像",
                            image: imageFromAssets
                        )
                    )
                    Spacer()
                }
                .navigationTitle("Title")
                .navigationBarTitleDisplayMode(.inline)
                .navigationDocument(imageFromURL)
        }

    }
}
extension UIImage: Transferable {
    public static var transferRepresentation: some TransferRepresentation {
        DataRepresentation(exportedContentType: .png) { image in
            if let pngData = image.pngData() {
                return pngData
            } else {
                throw ConversionError.failedToConvertToPNG
            }
        }
    }
    enum ConversionError: Error {
        case failedToConvertToPNG
    }
}

Simulator Screenshot - iPhone 15 Pro - 2024-09-26 at 22.37.27.jpeg

ここまでご覧いただきありがとうございました。間違い等ありましたらコメントにてご指摘お願いします。

個人的には、ShareLinkでUIActivityViewControllerのようにカスタムアクションを設定できるようになると良いなと思っているところです...

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?