import AVFoundation import Photos import SwiftUI struct AttachmentMediaPickerView: View { @State private var isCameraAccessGranted = AVCaptureDevice.authorizationStatus(for: .video) == .authorized @State private var isGalleryAccessGranted = PHPhotoLibrary.authorizationStatus() == .authorized @State private var photos = [PhotoView]() let gridSize = UIScreen.main.bounds.width / 3 var body: some View { let columns = Array(repeating: GridItem(.flexible(), spacing: 0), count: 3) ScrollView(showsIndicators: false) { LazyVGrid(columns: columns, spacing: 0) { // For camera if isCameraAccessGranted { ZStack { CameraView() .aspectRatio(1, contentMode: .fit) .frame(maxWidth: .infinity) Image(systemName: "camera") .resizable() .aspectRatio(contentMode: .fit) .frame(width: 40, height: 40) .foregroundColor(.white) .padding(8) .background(Color.black.opacity(0.5)) .clipShape(Circle()) .padding(8) } } else { Button { openAppSettings() } label: { ZStack { Rectangle() .fill(Color.Main.backgroundLight) .overlay { VStack { Image(systemName: "camera") .foregroundColor(.Material.tortoiseLight300) .font(.system(size: 30)) Text("Allow camera access") .foregroundColor(.Main.black) .font(.body3) } } .frame(height: 100) } } } // For pictures if isGalleryAccessGranted { ForEach(photos) { photo in photo } } else { Button { openAppSettings() } label: { ZStack { Rectangle() .fill(Color.Main.backgroundLight) .overlay { VStack { Image(systemName: "photo") .foregroundColor(.Material.tortoiseLight300) .font(.system(size: 30)) Text("Allow gallery access") .foregroundColor(.Main.black) .font(.body3) } } .frame(height: 100) } } } } } .onAppear { DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 0.2) { checkCameraAccess() checkGalleryAccess() } } } private func checkCameraAccess() { let status = AVCaptureDevice.authorizationStatus(for: .video) switch status { case .authorized: isCameraAccessGranted = true case .notDetermined: AVCaptureDevice.requestAccess(for: .video) { granted in DispatchQueue.main.async { self.isCameraAccessGranted = granted } } case .denied, .restricted: isCameraAccessGranted = false @unknown default: isCameraAccessGranted = false } } private func checkGalleryAccess() { let status = PHPhotoLibrary.authorizationStatus() switch status { case .authorized, .limited: isGalleryAccessGranted = true fetchImages() case .notDetermined: PHPhotoLibrary.requestAuthorization { status in DispatchQueue.main.async { self.isGalleryAccessGranted = status == .authorized if self.isGalleryAccessGranted { self.fetchImages() } } } case .denied, .restricted: isGalleryAccessGranted = false @unknown default: isGalleryAccessGranted = false } } private func fetchImages() { let fetchOptions = PHFetchOptions() fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] let assets = PHAsset.fetchAssets(with: .image, options: fetchOptions) let manager = PHImageManager.default() let option = PHImageRequestOptions() option.isSynchronous = true assets.enumerateObjects { asset, _, _ in manager.requestImage( for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .aspectFill, options: option ) { image, _ in image?.scaleAndCropImage(toExampleSize: CGSize(width: gridSize, height: gridSize), completion: { image in if let image { DispatchQueue.main.async { self.photos.append(PhotoView(image: image, gridSize: gridSize)) } } }) } } } func openAppSettings() { if let appSettingsUrl = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(appSettingsUrl) { UIApplication.shared.open(appSettingsUrl, completionHandler: nil) } } } private struct PhotoView: Identifiable, View { let id = UUID() let gridSize: CGFloat @State private var image: UIImage @State private var ready = false init(image: UIImage, gridSize: CGFloat) { self.image = image self.gridSize = gridSize } var body: some View { if ready { Image(uiImage: image) .resizable() .aspectRatio(contentMode: .fill) .frame(width: gridSize, height: gridSize) .clipped() } else { ZStack { Rectangle() .fill(Color.Main.backgroundLight) .overlay { ProgressView() } .frame(width: gridSize, height: gridSize) } .onAppear { image.scaleAndCropImage(toExampleSize: CGSize(width: gridSize, height: gridSize), completion: { image in if let image { self.image = image ready = true } }) } } } } class CameraUIView: UIView { var previewLayer: AVCaptureVideoPreviewLayer? override func layoutSubviews() { super.layoutSubviews() previewLayer?.frame = bounds } } struct CameraView: UIViewRepresentable { func makeUIView(context _: Context) -> CameraUIView { let view = CameraUIView() let captureSession = AVCaptureSession() guard let captureDevice = AVCaptureDevice.default(for: .video) else { return view } guard let input = try? AVCaptureDeviceInput(device: captureDevice) else { return view } captureSession.addInput(input) let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) previewLayer.videoGravity = .resizeAspectFill view.layer.addSublayer(previewLayer) view.previewLayer = previewLayer captureSession.startRunning() return view } func updateUIView(_ uiView: CameraUIView, context _: Context) { uiView.previewLayer?.frame = uiView.bounds } }