123 lines
4.5 KiB
Swift
123 lines
4.5 KiB
Swift
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(
|
|
Image(systemName: "person.crop.circle")
|
|
.font(.system(size: size * 0.5))
|
|
.foregroundColor(.gray)
|
|
)
|
|
.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
|
|
)
|
|
}
|
|
}
|
|
} |