feat: 选择图片
This commit is contained in:
parent
119b671838
commit
afa14f1fb2
Binary file not shown.
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,7 @@ struct UserInfo: View {
|
||||
@State private var notificationsEnabled = true
|
||||
@State private var darkModeEnabled = false
|
||||
@State private var showLogoutAlert = false
|
||||
@State private var avatarImage: UIImage? // Add this line
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
@ -40,17 +41,23 @@ struct UserInfo: View {
|
||||
|
||||
// Avatar
|
||||
ZStack {
|
||||
SVGImage(svgName: "Avatar")
|
||||
.frame(width: 225, height: 225)
|
||||
// Show either the SVG or the uploaded image
|
||||
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
|
||||
Image(systemName: "person.circle.fill")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 225, height: 225)
|
||||
.foregroundColor(.gray)
|
||||
.opacity(0.3)
|
||||
// Make sure the AvatarUploader is on top and covers the entire area
|
||||
AvatarUploader(selectedImage: $avatarImage, size: 200)
|
||||
.contentShape(Rectangle()) // This makes the entire area tappable
|
||||
}
|
||||
.frame(width: 200, height: 200)
|
||||
.padding(.vertical, 20)
|
||||
|
||||
// Buttons
|
||||
|
||||
@ -9,20 +9,38 @@ struct SVGImage: UIViewRepresentable {
|
||||
webView.isOpaque = false
|
||||
webView.backgroundColor = .clear
|
||||
webView.scrollView.isScrollEnabled = false
|
||||
webView.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
|
||||
// 尝试从根目录加载SVG文件
|
||||
if let url = Bundle.main.url(forResource: svgName, withExtension: "svg") {
|
||||
print("SVG URL found: \(url.path)")
|
||||
let request = URLRequest(url: url)
|
||||
webView.load(request)
|
||||
} else {
|
||||
print("Error: Failed to find SVG file with name: \(svgName).svg in main bundle")
|
||||
// 打印所有可用的SVG文件
|
||||
if let resourceURL = Bundle.main.resourceURL,
|
||||
let files = try? FileManager.default.contentsOfDirectory(at: resourceURL, includingPropertiesForKeys: nil) {
|
||||
let svgFiles = files.filter { $0.pathExtension.lowercased() == "svg" }
|
||||
print("Available SVG files: \(svgFiles)")
|
||||
}
|
||||
let htmlString = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<style>
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
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
|
||||
@ -31,5 +49,22 @@ struct SVGImage: UIViewRepresentable {
|
||||
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:
|
||||
// SVGImage(svgName: "Avatar")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user