Files
e-party-iOS/yana/Views/Components/ImagePickerWithPreview/ImagePickerWithPreviewView.swift
edwinQQQ ac0d622c97 feat: 更新Info.plist和AppSettingView以支持相机和图片选择功能
- 在Info.plist中新增相机使用说明,确保应用能够访问相机功能。
- 在AppSettingFeature中新增showImagePicker状态和setShowImagePicker动作,支持图片选择弹窗的显示。
- 在AppSettingView中整合图片选择与预览功能,优化用户体验。
- 更新MainView以简化导航逻辑,提升代码可读性与维护性。
- 在ImagePickerWithPreview组件中实现相机和相册选择功能,增强交互性。
2025-07-25 18:47:11 +08:00

189 lines
6.7 KiB
Swift

import SwiftUI
import ComposableArchitecture
import PhotosUI
public struct ImagePickerWithPreviewView: View {
let store: StoreOf<ImagePickerWithPreviewReducer>
let onUpload: ([UIImage]) -> Void
let onCancel: () -> Void
@State private var loadedImages: [UIImage] = []
@State private var isLoadingImages: Bool = false
public init(store: StoreOf<ImagePickerWithPreviewReducer>, onUpload: @escaping ([UIImage]) -> Void, onCancel: @escaping () -> Void) {
self.store = store
self.onUpload = onUpload
self.onCancel = onCancel
}
public var body: some View {
WithViewStore(store, observe: { $0 }) { viewStore in
ZStack {
Color.clear
LoadingView(isLoading: viewStore.inner.isLoading || isLoadingImages)
}
.background(.clear)
.modifier(ActionSheetModifier(viewStore: viewStore, onCancel: onCancel))
.modifier(CameraSheetModifier(viewStore: viewStore))
.modifier(PhotosPickerModifier(viewStore: viewStore, loadedImages: $loadedImages, isLoadingImages: $isLoadingImages))
.modifier(PreviewCoverModifier(viewStore: viewStore, loadedImages: loadedImages, onUpload: onUpload))
.modifier(ErrorToastModifier(viewStore: viewStore))
}
}
}
private struct LoadingView: View {
let isLoading: Bool
var body: some View {
if isLoading {
Color.black.opacity(0.4).ignoresSafeArea()
ProgressView("上传中...")
.progressViewStyle(CircularProgressViewStyle(tint: .white))
.foregroundColor(.white)
.padding()
.background(Color.black.opacity(0.7))
.cornerRadius(16)
}
}
}
private struct ActionSheetModifier: ViewModifier {
let viewStore: ViewStoreOf<ImagePickerWithPreviewReducer>
let onCancel: () -> Void
func body(content: Content) -> some View {
content.confirmationDialog(
"请选择图片来源",
isPresented: .init(
get: { viewStore.inner.showActionSheet },
set: { viewStore.send(.inner(.showActionSheet($0))) }
),
titleVisibility: .visible
) {
Button("拍照") { viewStore.send(.inner(.selectSource(.camera))) }
Button("从相册选择") { viewStore.send(.inner(.selectSource(.photoLibrary))) }
Button("取消", role: .cancel) { onCancel() }
}
}
}
private struct CameraSheetModifier: ViewModifier {
let viewStore: ViewStoreOf<ImagePickerWithPreviewReducer>
func body(content: Content) -> some View {
content.sheet(isPresented: .init(
get: { viewStore.inner.showCamera },
set: { viewStore.send(.inner(.setShowCamera($0))) }
)) {
CameraPicker { image in
viewStore.send(.inner(.cameraImagePicked(image)))
}
}
}
}
private struct PhotosPickerModifier: ViewModifier {
let viewStore: ViewStoreOf<ImagePickerWithPreviewReducer>
@Binding var loadedImages: [UIImage]
@Binding var isLoadingImages: Bool
func body(content: Content) -> some View {
content
.photosPicker(
isPresented: .init(
get: { viewStore.inner.showPhotoPicker },
set: { viewStore.send(.inner(.setShowPhotoPicker($0))) }
),
selection: .init(
get: { viewStore.inner.selectedPhotoItems },
set: { viewStore.send(.inner(.photoPickerItemsChanged($0))) }
),
maxSelectionCount: {
switch viewStore.inner.selectionMode {
case .single: return 1
case .multiple(let max): return max
}
}(),
matching: .images
)
.onChange(of: viewStore.inner.selectedPhotoItems) { items in
guard !items.isEmpty else { return }
isLoadingImages = true
loadedImages = []
let group = DispatchGroup()
var tempImages: [UIImage] = []
for item in items {
group.enter()
item.loadTransferable(type: Data.self) { result in
defer { group.leave() }
if let data = try? result.get(), let uiImage = UIImage(data: data) {
tempImages.append(uiImage)
}
}
}
DispatchQueue.global().async {
group.wait()
DispatchQueue.main.async {
loadedImages = tempImages
isLoadingImages = false
viewStore.send(.inner(.setLoading(false)))
}
}
}
}
}
private struct PreviewCoverModifier: ViewModifier {
let viewStore: ViewStoreOf<ImagePickerWithPreviewReducer>
let loadedImages: [UIImage]
let onUpload: ([UIImage]) -> Void
func body(content: Content) -> some View {
content.fullScreenCover(isPresented: .init(
get: { viewStore.inner.showPreview },
set: { _ in }
)) {
ImagePreviewView(
images: previewImages,
currentIndex: .init(
get: { viewStore.inner.previewIndex },
set: { viewStore.send(.inner(.setPreviewIndex($0))) }
),
onConfirm: {
viewStore.send(.inner(.previewConfirm))
onUpload(previewImages)
},
onCancel: {
viewStore.send(.inner(.previewCancel))
}
)
}
}
private var previewImages: [UIImage] {
if let camera = viewStore.inner.cameraImage {
return [camera]
}
if !loadedImages.isEmpty {
return loadedImages
}
return []
}
}
private struct ErrorToastModifier: ViewModifier {
let viewStore: ViewStoreOf<ImagePickerWithPreviewReducer>
func body(content: Content) -> some View {
content.overlay(
Group {
if let error = viewStore.inner.errorMessage {
VStack {
Spacer()
Text(error)
.foregroundColor(.red)
.padding()
.background(Color.white)
.cornerRadius(12)
.padding(.bottom, 40)
}
}
}
)
}
}