iOSアプリ開発で強引にフォルダ指定して使用してみた

# 今回の記事は失敗例となります 

### FileList_Get_testApp.swift
```
import SwiftUI
import SwiftData

@main
struct FileList_Get_testApp: App {
    @EnvironmentObject var userSettings: UserSettings // ✅ `UserSettings` を環境オブジェクトとして使用

    var body: some Scene {
        WindowGroup {
            ContentView().environmentObject(UserSettings())
        }
    }
}

```

## FolderPickerView.swift
```
import SwiftUI
import UniformTypeIdentifiers

struct FolderPickerView: View {
    @State private var videoFiles: [URL] = [] // ✅ フォルダ内の動画ファイルを保存
    @State private var isDocumentPickerPresented = false
    @State private var folderURL: URL?
    @EnvironmentObject var userSettings: UserSettings // ✅ `UserSettings` を環境オブジェクトとして使用
    
    var body: some View {
        NavigationStack {
            VStack {
                Text("フォルダ内の動画一覧")
                    .font(.title)
                    .padding()

                if videoFiles.isEmpty {
                    Text("フォルダを選択してください")
                        .foregroundColor(.gray)
                } else {
                    List(videoFiles, id: \.self) { file in
                        NavigationLink(destination: VideoPlayerView(videoURL: file).environmentObject(UserSettings())) {
                            Text(file.lastPathComponent)
                        }
                    }
                }

                Button("フォルダを選択") {
                    isDocumentPickerPresented = true
                }
                .padding()
            }
            .navigationTitle("フォルダ選択")
            .sheet(isPresented: $isDocumentPickerPresented) {
                FolderPicker(videoFiles: $videoFiles, folderURL: $folderURL)
            }
            .onAppear {
                loadFolderBookmark() // ✅ アプリ再起動後にフォルダを復元
            }
        }
    }

    // ✅ `UserDefaults` からフォルダのブックマークを復元
    private func loadFolderBookmark() {
        guard let bookmarkData = UserDefaults.standard.data(forKey: "SavedFolderBookmark") else {
            print("ℹ️ 保存されたブックマークデータがありません")
            return
        }

        do {
            var isStale = false
            let restoredURL = try URL(resolvingBookmarkData: bookmarkData, options: [], relativeTo: nil, bookmarkDataIsStale: &isStale)

            if isStale {
                print("⚠️ ブックマークが古いため、再保存が必要")
                return
            }

            if restoredURL.startAccessingSecurityScopedResource() {
                self.folderURL = restoredURL
                print("✅ フォルダブックマークを復元しました: \(restoredURL)")
            } else {
                print("❌ Security-Scoped URL にアクセスできません")
            }
        } catch {
            print("❌ フォルダのブックマーク復元エラー: \(error.localizedDescription)")
        }
    }
}

// ✅ Security-Scoped URL を使ってフォルダの内容を取得
struct FolderPicker: UIViewControllerRepresentable {
    @Binding var videoFiles: [URL]
    @Binding var folderURL: URL?

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIViewController(context: Context) -> UIDocumentPickerViewController {
        let picker = UIDocumentPickerViewController(forOpeningContentTypes: [UTType.folder])
        picker.delegate = context.coordinator
        picker.allowsMultipleSelection = false
        return picker
    }

    func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {}

    class Coordinator: NSObject, UIDocumentPickerDelegate {
        let parent: FolderPicker

        init(_ parent: FolderPicker) {
            self.parent = parent
        }

        func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
            guard let selectedFolderURL = urls.first else { return }
            print("✅ 選択したフォルダ: \(selectedFolderURL)")

            // ✅ Security-Scoped URL の許可
            if selectedFolderURL.startAccessingSecurityScopedResource() {
                defer { selectedFolderURL.stopAccessingSecurityScopedResource() }

                // ✅ フォルダのブックマークを保存
                saveFolderBookmark(folderURL: selectedFolderURL)

                do {
                    let fileManager = FileManager.default
                    let files = try fileManager.contentsOfDirectory(at: selectedFolderURL, includingPropertiesForKeys: nil)

                    let videoFiles = files.filter { $0.pathExtension.lowercased() == "mp4" || $0.pathExtension.lowercased() == "mov" }

                    DispatchQueue.main.async {
                        self.parent.videoFiles = videoFiles
                        self.parent.folderURL = selectedFolderURL
                    }
                } catch {
                    print("❌ フォルダの内容を取得できませんでした: \(error.localizedDescription)")
                }
            } else {
                print("❌ Security-Scoped URL のアクセスに失敗しました")
            }
        }

        // ✅ `Security-Scoped URL` を `UserDefaults` に保存
        private func saveFolderBookmark(folderURL: URL) {
            do {
                let bookmarkData = try folderURL.bookmarkData(options: [], includingResourceValuesForKeys: nil, relativeTo: nil)
                UserDefaults.standard.set(bookmarkData, forKey: "SavedFolderBookmark")
                print("✅ フォルダのブックマークを保存しました")
            } catch {
                print("❌ フォルダのブックマーク保存エラー: \(error.localizedDescription)")
            }
        }
    }
}

```

### VideoPlayerView.swift
```
import SwiftUI
import AVKit

struct VideoPlayerView: View {
    let videoURL: URL
    @State private var fileStatus: String = "🔍 パスを確認中..."
    
    var body: some View {
        VStack {
            VideoPlayer(player: AVPlayer(url: videoURL))
                .frame(height: 400)
                .cornerRadius(12)
                .padding()
                .onAppear {
                    checkFileStatus() // ✅ ファイルの存在チェック
                }

            Text("動画を再生中")
                .font(.headline)
                .padding()

            VStack {
                Text("📂 ファイルパス:")
                    .font(.caption)
                Text(videoURL.absoluteString) // ✅ `absoluteString` でファイルの正確なパスを表示
                    .font(.caption)
                    .foregroundColor(.gray)
                    .padding(.bottom, 4)

                Text("📝 ステータス: \(fileStatus)")
                    .font(.caption)
                    .foregroundColor(fileStatus.contains("✅") ? .green : .red)
            }
            .padding()
        }
        .navigationTitle("動画再生")
    }

    // ✅ `Security-Scoped URL` を適用し、フォルダ内のファイルを確認
    private func checkFileStatus() {
        let fileManager = FileManager.default
        let path = videoURL.path

        // ✅ `Security-Scoped URL` のアクセス許可を確認
        if videoURL.startAccessingSecurityScopedResource() {
            defer { videoURL.stopAccessingSecurityScopedResource() }

            do {
                let parentDirectory = videoURL.deletingLastPathComponent() // ✅ フォルダのURL
                let filesInDirectory = try fileManager.contentsOfDirectory(at: parentDirectory, includingPropertiesForKeys: nil)

                if filesInDirectory.contains(videoURL) {
                    fileStatus = "✅ ファイルが見つかりました"
                    print("✅ ファイルが存在: \(path)")
                } else {
                    fileStatus = "❌ フォルダ内にファイルが見つかりません"
                    print("❌ フォルダ内にファイルが見つからない: \(path)")
                }
            } catch {
                fileStatus = "❌ フォルダの内容を取得できませんでした"
                print("❌ `FileManager` のエラー: \(error.localizedDescription)")
            }
        } else {
            fileStatus = "❌ Security-Scoped URL にアクセスできません"
            print("❌ Security-Scoped URL にアクセスできません: \(path)")
        }
    }
}

```
### UserSettings.swift
```
//import AVFoundation
import SwiftUI
import AVKit

class UserSettings: ObservableObject {
    @Published var player = AVPlayer()

    // ✅ 動画URLを設定し、AVPlayerItem を更新
    func setVideoURL(_ url: URL) {
        print("🎥 設定する動画URL: \(url.absoluteString)")

        // ✅ Security-Scoped URL のアクセス許可
        if url.startAccessingSecurityScopedResource() {
            defer { url.stopAccessingSecurityScopedResource() } // ✅ 使用後に解放

            let playerItem = AVPlayerItem(url: url)
            player.replaceCurrentItem(with: playerItem) // ✅ `AVPlayerItem` を設定
        } else {
            print("❌ Security-Scoped URL にアクセスできません")
        }
    }

    // ✅ 再生開始
    func play() {
        player.play()
    }

    // ✅ 一時停止
    func pause() {
        player.pause()
    }
}
```