今回の記事は失敗例となります
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()
}
}