import SwiftUI import PhotosUI import os.log import AVKit struct MediaPickerWithLogging: UIViewControllerRepresentable { @Binding var selectedMedia: [MediaType] let selectionLimit: Int let onDismiss: (() -> Void)? func makeUIViewController(context: Context) -> PHPickerViewController { var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared()) configuration.filter = .any(of: [.videos, .images]) configuration.selectionLimit = selectionLimit configuration.preferredAssetRepresentationMode = .current let picker = PHPickerViewController(configuration: configuration) picker.delegate = context.coordinator return picker } func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, PHPickerViewControllerDelegate { let parent: MediaPickerWithLogging private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.example.app", category: "MediaPickerWithLogging") init(_ parent: MediaPickerWithLogging) { self.parent = parent } func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { guard !results.isEmpty else { parent.onDismiss?() return } var processedMedia: [MediaType] = [] let group = DispatchGroup() for result in results { let itemProvider = result.itemProvider if itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) { group.enter() processImage(itemProvider: itemProvider) { media in if let media = media { processedMedia.append(media) } group.leave() } } else if itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) { group.enter() processVideo(itemProvider: itemProvider) { media in if let media = media { processedMedia.append(media) } group.leave() } } } group.notify(queue: .main) { self.parent.selectedMedia = processedMedia self.printMediaInfo(media: processedMedia) self.parent.onDismiss?() } } private func processImage(itemProvider: NSItemProvider, completion: @escaping (MediaType?) -> Void) { itemProvider.loadObject(ofClass: UIImage.self) { (object, error) in if let image = object as? UIImage { completion(.image(image)) } else { self.logger.error("Failed to load image: \(error?.localizedDescription ?? "Unknown error")") completion(nil) } } } private func processVideo(itemProvider: NSItemProvider, completion: @escaping (MediaType?) -> Void) { itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { url, error in guard let videoURL = url, error == nil else { self.logger.error("Failed to load video: \(error?.localizedDescription ?? "Unknown error")") completion(nil) return } let tempDirectory = FileManager.default.temporaryDirectory let targetURL = tempDirectory.appendingPathComponent("\(UUID().uuidString).\(videoURL.pathExtension)") do { if FileManager.default.fileExists(atPath: targetURL.path) { try FileManager.default.removeItem(at: targetURL) } try FileManager.default.copyItem(at: videoURL, to: targetURL) MediaUtils.extractFirstFrame(from: targetURL) { result in switch result { case .success(let thumbnail): completion(.video(targetURL, thumbnail)) case .failure(let error): self.logger.error("Failed to extract video thumbnail: \(error.localizedDescription)") completion(.video(targetURL, nil)) } } } catch { self.logger.error("Failed to copy video file: \(error.localizedDescription)") completion(nil) } } } private func printMediaInfo(media: [MediaType]) { print("=== Selected Media Information ===") for (index, media) in media.enumerated() { print("\nItem \(index + 1):") switch media { case .image(let image): print("Type: Image") print("Dimensions: \(Int(image.size.width))x\(Int(image.size.height))") if let data = image.jpegData(compressionQuality: 1.0) { print("File Size: \(formatFileSize(Int64(data.count)))") } case .video(let url, _): print("Type: Video") print("File Name: \(url.lastPathComponent)") print("File Path: \(url.path)") if let attributes = try? FileManager.default.attributesOfItem(atPath: url.path), let fileSize = attributes[.size] as? Int64 { print("File Size: \(formatFileSize(fileSize))") } let asset = AVURLAsset(url: url) let duration = asset.duration.seconds print("Duration: \(formatTimeInterval(duration))") if let track = asset.tracks(withMediaType: .video).first { let size = track.naturalSize print("Video Dimensions: \(Int(size.width))x\(Int(size.height))") } } } print("================================\n") } private func formatFileSize(_ bytes: Int64) -> String { let formatter = ByteCountFormatter() formatter.allowedUnits = [.useBytes, .useKB, .useMB, .useGB] formatter.countStyle = .file return formatter.string(fromByteCount: bytes) } private func formatTimeInterval(_ interval: TimeInterval) -> String { let formatter = DateComponentsFormatter() formatter.allowedUnits = [.hour, .minute, .second] formatter.zeroFormattingBehavior = .pad return formatter.string(from: interval) ?? "00:00" } } }