import SwiftUI import PhotosUI struct PhotoPicker: UIViewControllerRepresentable { @Binding var selectedImages: [UIImage] let selectionLimit: Int let filter: PHPickerFilter init(selectedImages: Binding<[UIImage]>, selectionLimit: Int = 1, filter: PHPickerFilter = .images) { self._selectedImages = selectedImages self.selectionLimit = selectionLimit self.filter = filter } func makeUIViewController(context: Context) -> PHPickerViewController { var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared()) configuration.filter = filter 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: PhotoPicker init(_ parent: PhotoPicker) { self.parent = parent } func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { parent.selectedImages.removeAll() let group = DispatchGroup() var loadedImages: [Int: UIImage] = [:] for (index, result) in results.enumerated() { group.enter() if result.itemProvider.canLoadObject(ofClass: UIImage.self) { result.itemProvider.loadObject(ofClass: UIImage.self) { (image, error) in if let image = image as? UIImage { loadedImages[index] = image } group.leave() } } else { group.leave() } } group.notify(queue: .main) { // Sort the images by their original index to maintain selection order let sortedImages = loadedImages.sorted { $0.key < $1.key }.map { $0.value } self.parent.selectedImages.append(contentsOf: sortedImages) // Dismiss the picker picker.dismiss(animated: true) } } } } // MARK: - Avatar Uploader Component struct AvatarUploader: View { @Binding var selectedImage: UIImage? let size: CGFloat @State private var isImagePickerPresented = false var body: some View { Button(action: { isImagePickerPresented = true }) { ZStack { // Avatar Image or Placeholder if let selectedImage = selectedImage { Image(uiImage: selectedImage) .resizable() .scaledToFill() .frame(width: size, height: size) .clipShape(RoundedRectangle(cornerRadius: size * 0.1)) } else { // Default avatar container Color.gray.opacity(0.1) .frame(width: size, height: size) .overlay( SVGImage(svgName: "Avatar") .frame(width: size * 0.8, height: size * 0.8) ) .clipShape(RoundedRectangle(cornerRadius: size * 0.1)) .overlay( RoundedRectangle(cornerRadius: size * 0.1) .stroke(Color.gray.opacity(0.3), lineWidth: 1) ) } } .frame(width: size, height: size) .contentShape(Rectangle()) // Make the entire area tappable } .buttonStyle(PlainButtonStyle()) // Remove button highlight effect .sheet(isPresented: $isImagePickerPresented) { PhotoPicker( selectedImages: Binding( get: { [selectedImage].compactMap { $0 } }, set: { images in selectedImage = images.first } ), selectionLimit: 1 ) } } }