SwiftUI で ファイルを外部にShareするには
目次
以下のような感じで SwiftUI で アプリケーション内部のファイルを外部に Share する方法です。
UIActivityViewController
を使うんだろうな、というのは少しググるとわかるのですが SwiftUI 初心者には具体的にどう書いたら良いかわからなかったので書き残しておきます。
環境
- MacOS:
12.1
- Xcode:
Version 13.2.1 (13C100)
- Swift:
5.5.2
Case1: SwiftUI で UIActivityViewController を呼び出す方法(シンプル版)
SwiftUI から UIActivityViewController
を直接は扱えないので、 UIViewControllerRepresentable
というのを経由して呼び出すと良いようです。
Step1: UIActivityViewController
を扱う ActivityViewController
を作る
// ActivityViewController.swift
import SwiftUI
struct ActivityViewController: UIViewControllerRepresentable {
var activityItems: [Any]
var applicationActivities: [UIActivity]? = nil
var completionHandler: ((Bool) -> Void)? = nil
func makeUIViewController(context: UIViewControllerRepresentableContext<Self>) -> UIActivityViewController {
let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
if self.completionHandler != nil {
controller.completionWithItemsHandler = { (activityType, completed, returnedItems, error) in
self.completionHandler?(completed)
}
}
return controller
}
func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<Self>) {}
}
completionHandler
は、共有が終わったら呼び出されます(今回は使わなかったですが…)。
Step2: .sheet
を使って、ボタンを押したら出現するようにする
例えば、こんな感じになります。
struct SoundDetailView: View {
@State var showingSheet: Bool = false
let fileUrl: URL
var body: some View {
Button {
showingSheet = true
} label: {
Text("共有する!")
}
.sheet(isPresented: $showingSheet) {
createFileShareView(fileUrl)
}
}
}
func createFileShareView(_ url: URL) -> some View {
return ActivityViewController(activityItems: [url])
}
Case2: SwiftUI で UIActivityViewController を呼び出す方法(ファイル名を指定したい場合)
共有されるときのファイル名を指定したい場合があります。が、API で指定することはできないようです。 代わりに、ファイル名がデフォルトの共有ファイル名になるので、事前にコピーして、終わったら削除すれば一応実現できます。 ちなみに、Symlink ではダメでした。
先程の createFileShareView
を以下のようにするとできるようになります。
...略
.sheet(isPresented: $showingSheet) {
createFileShareView(fileUrl, newName: "awesome_file")
}
...略
func createFileShareView(_ origUrl: URL, newName: String) -> some View {
let ext = origUrl.pathExtension
let newUrl = origUrl.deletingLastPathComponent().appendingPathComponent(newName, isDirectory: false).appendingPathExtension(ext)
if FileManager.default.fileExists(atPath: newUrl.path) {
try! FileManager.default.removeItem(at: newUrl)
}
try! FileManager.default.copyItem(at: origUrl, to: newUrl)
return ActivityViewController(activityItems: [newUrl])
.onDisappear {
if FileManager.default.fileExists(atPath: newUrl.path) {
try! FileManager.default.removeItem(at: newUrl)
}
}
}
.onDisappear()
でコピーしたファイルを消すのが一つポイントです。
ActivityViewController
の completionHandler
で消してしまうと、1 回の Sheet 表示中にユーザーが何度も共有しようとした場合に、
1 回目の終了後にファイルが消えてしまうので 2 回目以降が失敗してしまいます。