
- 在Info.plist中新增相机使用说明,确保应用能够访问相机功能。 - 在AppSettingFeature中新增showImagePicker状态和setShowImagePicker动作,支持图片选择弹窗的显示。 - 在AppSettingView中整合图片选择与预览功能,优化用户体验。 - 更新MainView以简化导航逻辑,提升代码可读性与维护性。 - 在ImagePickerWithPreview组件中实现相机和相册选择功能,增强交互性。
189 lines
6.7 KiB
Swift
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)
|
|
}
|
|
}
|
|
}
|
|
)
|
|
}
|
|
}
|