
ADPの登録が完了してさっそくiCouldにファイル保存しようと思って調べているときに見つけたプロジェクトのテンプレートである「Document App」。作成してPreviewを見るも「なんのこっちゃ」とスルーしていたが、調べてみるとかなり使いやすいフレームワークっぽいので、その調査と備忘録になります。

Document Appで作成したテンプレアプリを調べる



アプリを起動すると「ファイル」アプリでよく見る画面が表示されて「あれ?起動するアプリ間違えたかな」と思ったけどDocment Appではこのファイル選択画面から起動するらしいです。MacのFinderやWindowsのExplorer的な感じで利用できるインターフェースです。「データを読み書きすること」を目的としたアプリであれば、この機能を使うと簡単にiCouldにアクセスできます。もちろんADP登録とiCloudの設定が必要ですが。
DocumentGroup

import SwiftUI
@main
struct SampleDocumentApp: App {
var fileProvider: FileProvider = FileProvider()
var body: some Scene {
DocumentGroup(newDocument: SampleDocumentDocument()) { file in
let _ = print("\(String(describing: file.fileURL))")
ContentView(document: file.$document)
.environment(fileProvider)
}
}
}
上記のSampleDocumentDocument(サンプルアプリ作ったときにプロジェクト名をSampleDocumentにしたら後ろにDocumentが付いちゃった)がFileDocumentを継承していれば「Create Document」で新規作成時にファイルが作成されて閲覧・編集ができるような仕組みです。「fileURL」が参照可能ですが、「そこから書き込みするとやべぇ事になる」との事なので基本は「FileDocument」経由でアクセスするのが正解だそうです。ネットワーク経由で排他とか考えたくないですからね。printで調べたiCloud DriveのURLたちです。ちなみにfileProviderはデバッグ用に作ったクラスで、最後に出てきます。
iCloud系のURLはすべて頭に下記のURLが付きます。xxxxはユーザーIDでXXXXはデバイスのIDですかね。シュミレーターを利用しいてますが、シュミレーター側でもApple IDでログインしてiCloudを有効にする必要があります。
file:///Users/xxxx/Library/Developer/CoreSimulator/Devices/XXXX/data/Library/Mobile%20Documents
iCould DriveのURL
file:///Users/xxxx/Library/Developer/CoreSimulator/Devices/XXXX/data/Library/Mobile%20Documents /com~apple~CloudDocs/
一番悩んだのがiCloud DriveのURLをどのように取得するか。開けない事にはURLを知ることができないので、DocumentGroupは簡単に実装できるのでものすごい便利でした。このコンテナ名はネットではよく見るのですが、公式には見つけられなかった。探し方が悪いのかな。とりあえずiCloud Driveの直下がこのURLになります。
アプリフォルダのURL
file:///Users/xxxx/Library/Developer/CoreSimulator/Devices/XXXX/data/Library/Mobile%20Documents/iCloud~org~m7e~sample~container/Documents/
XcodeでiCloudの機能を追加して、Servicesの「iCloud Documents」「CloudKit」にチェック入れてからContainersにCloudKit Consoleで追加したコンテナID(?)にチェックを入れると、このコンテナがデフォルトで出来上がるようです。ユーザーがアプリから見た場合のフォルダはiCloud Drive/アプリ名/です。
書類フォルダのURL
file:///Users/xxxx/Library/Developer/CoreSimulator/Devices/XXXX/data/Library/Mobile%20Documents/com~apple~CloudDocs/Documents/
元々はこの書類フォルダにcsvファイルを投げ込むために色々と調べていました。いろんな「Documents」フォルダが存在しているので、ややこしいですね。
このiPhone内のURL
file:///Users/xxxx/Library/Developer/CoreSimulator/Devices/XXXX/data/Containers/Shared/AppGroup/XXXXX/File%20Provider%20Storage/
ファイルアプリで表示したときに「このiPhone内」と表示されるURLです。Documents経由では簡単にアクセスできるけど、FileManager経由ではどうなんでしょう。
FileDocument


import SwiftUI
import UniformTypeIdentifiers
extension UTType {
static var exampleText: UTType {
UTType(importedAs: "com.example.plain-text")
}
static var csv: UTType {
UTType(importedAs: "org.m7e.plain-text")
}
}
struct SampleDocumentDocument: FileDocument {
var text: String
init(text: String = "Hello, world!") {
self.text = text
}
static var readableContentTypes: [UTType] { [.exampleText, .csv] }
init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let string = String(data: data, encoding: .utf8)
else {
throw CocoaError(.fileReadCorruptFile)
}
text = string
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let data = text.data(using: .utf8)!
return .init(regularFileWithContents: data)
}
}
FileDocumentを継承したオブジェクトを用意します。といってもプロジェクト作成時に「Document App」を選択すると勝手にテンプレートが作成されるので、それに調査用に「csv拡張子のファイル」を読み込めるように追加しています。ここでUTTypeを追加しないと「ファイルは見えるけど選択ができない」状態になります。

そのまま実装するとエラーが出るので「info.plist」にテンプレを参考にCSVの情報を追加して完了。ファイルを読み込む事ができます。
おまけ:FileManager経由でiCould Driveを参照する
Documentsの操作により、ファイルのパスが分かりました。パスが分かればどのようにアクセスするのか調べるのは可能なので、調べた事を記録しておきます。
iCloud Driveへのアクセス
調べててわかったことはADP登録前に調べたディレクトリ関連のIFは使わないという事。以前のイメージは「iCloud機能の有効化」→「FileManager.urls(for: in:)でiCloudのURLが追加取得可能」と勝手に想定していましたが、実際は「iCloudの有効化」→「FileManager.url(forUbiquityContainerIdentifier:)でiCloudのURL取得」でした。URL取得後は通常のファイルアプリと同様に操作可能なようです。
import Foundation
@Observable
class FileProvider {
var fileName: String = "sample.txt"
private var myiCloudContainerURL: URL?
private var isiCouldAvalable: Bool = false
private var myLocalContainerURL: URL?
init?() {
guard FileManager.default.ubiquityIdentityToken != nil else {
return nil
}
DispatchQueue.global().async {
let ubiquityContainerID = "com.apple.CloudDocs"
if let url = FileManager.default.url(forUbiquityContainerIdentifier: ubiquityContainerID) {
DispatchQueue.main.async {
print("container URL: \(url)")
self.myiCloudContainerURL = url
self.isiCouldAvalable = true
print("iCould is avaialble.")
}
return
}
print("Error to retrieve iCloud container URL!!!")
}
self.myLocalContainerURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let error = "Local URL error"
print("Local URL \(self.myLocalContainerURL?.absoluteString ?? error)")
}
func save(content: String) {
if isiCouldAvalable {
let filePath = self.myiCloudContainerURL?.appendingPathComponent(self.fileName)
do {
try content.write(to: filePath!, atomically: true, encoding: .utf8)
print("Success!! \(String(describing: filePath))")
} catch {
fatalError("Save iCloud FATAL ERROR!!! \(error)")
}
}
let filePath = self.myLocalContainerURL?.appendingPathComponent("local.csv")
do {
try content.write(to: filePath!, atomically: true, encoding: .utf8)
print("Success!! \(String(describing: filePath))")
} catch {
fatalError("Save Local FATAL ERROR!!! \(error)")
}
}
func load(fileName: String) -> String {
if isiCouldAvalable {
let filePath = self.myiCloudContainerURL?.appendingPathComponent(fileName)
guard let content = try? String(String(contentsOf: filePath!, encoding: .utf8)) else {
fatalError("Read FATAL ERROR!!!")
}
return content
}
return "load Error!!"
}
}
File Provider(本物)と「このiPhone内フォルダ」

特に考えもなくデバッグ用のクラス「File Provider」を作成していましたが、「このiPhone内」のクラスにアクセスした際に「File Provider Storage」なる物が含まれたURLがあったので調べたら本物がいました(汗。少し調べてみましたが、今回はスルーする事にしました。
で、「このiPhone内」にアクセスする方法を調べました。FileProviderの説明の中に「ローカルで共有したい場合はFileProviderいらないよ」みたいな記述があって、その内容としては普通に「for: .documentDirectory , in: .userDomainMask」のAPIでURLを取得して使う事と「info.plist」に以下の2行を追加して「YES」で設定するだけでした。この設定をしないと自前のアプリからは参照できても、ファイルアプリから参照できないようです。
- Application supports iTunes file sharing (UIFileSharingEnabled)
- Supports opening documents in place (LSSupportsOpeningDocumentsInPlace)
実際は「このiPhone内」の下にアプリのフォルダーが作成されるのとURLはファイルアプリからアクセスしたのとは違いますが、ファイルアプリは「FileProvider」を利用してアクセスしているのでしょう。
file:///Users/xxxx/Library/Developer/CoreSimulator/Devices/XXXX/data/Containers/Data/Application/YYYY/Documents/local.csv

info.plistの説明は検索するのが大変ですね。本家でフィルターかけて探しています。
雑感
これでiCloudを使ったファイルアクセスは可能となったので、早速アプリの実装にかかろうと思います。前回までのUIをやめて今回のSwfitUI Documentsを使って、ドキュメントのビューをテーブルビュー的なものにし、テーブルデータをjsonファイルでまとめてたのをSwiftDataを利用するように実装を変更しようと思います。結構やることあるな…ほぼ新規実装なのか(汗。今月中に広告付けてアプリをリリース手続き(審査があるらしいので)まで進めようと思っているのですが、おそらくギリギリですな。
コメント