feat: 选择图片

This commit is contained in:
jinyaqiu 2025-08-19 14:26:53 +08:00
parent 119b671838
commit afa14f1fb2
4 changed files with 185 additions and 21 deletions

View File

@ -0,0 +1,122 @@
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
)
}
}
}

View File

@ -9,6 +9,7 @@ struct UserInfo: View {
@State private var notificationsEnabled = true @State private var notificationsEnabled = true
@State private var darkModeEnabled = false @State private var darkModeEnabled = false
@State private var showLogoutAlert = false @State private var showLogoutAlert = false
@State private var avatarImage: UIImage? // Add this line
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
@ -40,17 +41,23 @@ struct UserInfo: View {
// Avatar // Avatar
ZStack { ZStack {
SVGImage(svgName: "Avatar") // Show either the SVG or the uploaded image
.frame(width: 225, height: 225) if let avatarImage = avatarImage {
Image(uiImage: avatarImage)
.resizable()
.scaledToFill()
.frame(width: 200, height: 200)
.clipShape(Circle())
} else {
SVGImage(svgName: "Avatar")
.frame(width: 200, height: 200)
}
// Fallback in case SVG fails to load // Make sure the AvatarUploader is on top and covers the entire area
Image(systemName: "person.circle.fill") AvatarUploader(selectedImage: $avatarImage, size: 200)
.resizable() .contentShape(Rectangle()) // This makes the entire area tappable
.scaledToFit()
.frame(width: 225, height: 225)
.foregroundColor(.gray)
.opacity(0.3)
} }
.frame(width: 200, height: 200)
.padding(.vertical, 20) .padding(.vertical, 20)
// Buttons // Buttons

View File

@ -9,20 +9,38 @@ struct SVGImage: UIViewRepresentable {
webView.isOpaque = false webView.isOpaque = false
webView.backgroundColor = .clear webView.backgroundColor = .clear
webView.scrollView.isScrollEnabled = false webView.scrollView.isScrollEnabled = false
webView.scrollView.contentInsetAdjustmentBehavior = .never
// SVG
if let url = Bundle.main.url(forResource: svgName, withExtension: "svg") { if let url = Bundle.main.url(forResource: svgName, withExtension: "svg") {
print("SVG URL found: \(url.path)") let htmlString = """
let request = URLRequest(url: url) <!DOCTYPE html>
webView.load(request) <html>
} else { <head>
print("Error: Failed to find SVG file with name: \(svgName).svg in main bundle") <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
// SVG <style>
if let resourceURL = Bundle.main.resourceURL, body, html {
let files = try? FileManager.default.contentsOfDirectory(at: resourceURL, includingPropertiesForKeys: nil) { margin: 0;
let svgFiles = files.filter { $0.pathExtension.lowercased() == "svg" } padding: 0;
print("Available SVG files: \(svgFiles)") width: 100%;
} height: 100%;
overflow: hidden;
}
svg {
width: 100%;
height: 100%;
display: block;
}
</style>
</head>
<body>
<div style="width:100%; height:100%; display:flex; align-items:center; justify-content:center;">
<img src="\(url.absoluteString)" style="max-width:100%; max-height:100%;" />
</div>
</body>
</html>
"""
webView.loadHTMLString(htmlString, baseURL: nil)
} }
return webView return webView
@ -31,5 +49,22 @@ struct SVGImage: UIViewRepresentable {
func updateUIView(_ uiView: WKWebView, context: Context) {} func updateUIView(_ uiView: WKWebView, context: Context) {}
} }
// MARK: - View Modifier for SVG Image
struct SVGImageModifier: ViewModifier {
let size: CGSize
func body(content: Content) -> some View {
content
.frame(width: size.width, height: size.height)
.aspectRatio(contentMode: .fit)
}
}
extension View {
func svgImageStyle(size: CGSize) -> some View {
self.modifier(SVGImageModifier(size: size))
}
}
// Usage: // Usage:
// SVGImage(svgName: "Avatar") // SVGImage(svgName: "Avatar")