feat: 增强编辑动态功能,支持图片选择与处理

- 在EditFeedFeature中新增图片选择相关状态和动作,支持用户选择和处理最多9张图片。
- 在EditFeedView中集成ModernImageSelectionGrid组件,优化图片选择界面。
- 更新CreateFeedView以支持图片选择功能,提升用户体验。
- 实现图片处理逻辑,确保用户能够方便地添加和删除图片。
This commit is contained in:
edwinQQQ
2025-07-22 18:06:10 +08:00
parent 4eb01bde7c
commit 2a02553015
3 changed files with 187 additions and 60 deletions

View File

@@ -1,6 +1,7 @@
import Foundation
import ComposableArchitecture
import SwiftUI
import PhotosUI // PhotosUI
@Reducer
struct EditFeedFeature {
@@ -11,9 +12,24 @@ struct EditFeedFeature {
var errorMessage: String? = nil
var shouldDismiss: Bool = false
var selectedImages: [PhotosPickerItem] = []
var processedImages: [UIImage] = []
var canAddMoreImages: Bool {
processedImages.count < 9
}
var canPublish: Bool {
!content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !isLoading
}
// EquatableselectedImagesPhotosPickerItemEquatable
static func == (lhs: State, rhs: State) -> Bool {
lhs.content == rhs.content &&
lhs.isLoading == rhs.isLoading &&
lhs.errorMessage == rhs.errorMessage &&
lhs.shouldDismiss == rhs.shouldDismiss &&
lhs.processedImages == rhs.processedImages &&
lhs.selectedImages.count == rhs.selectedImages.count
}
}
enum Action {
@@ -23,6 +39,11 @@ struct EditFeedFeature {
case clearError
case dismissView
case clearDismissFlag
// Action
case photosPickerItemsChanged([PhotosPickerItem])
case processPhotosPickerItems([PhotosPickerItem])
case updateProcessedImages([UIImage])
case removeImage(Int)
}
@Dependency(\.apiService) var apiService
@@ -83,6 +104,36 @@ struct EditFeedFeature {
case .clearDismissFlag:
state.shouldDismiss = false
return .none
case .photosPickerItemsChanged(let items):
state.selectedImages = items
return .run { send in
await send(.processPhotosPickerItems(items))
}
case .processPhotosPickerItems(let items):
let currentImages = state.processedImages
return .run { send in
var newImages = currentImages
for item in items {
guard let data = try? await item.loadTransferable(type: Data.self),
let image = UIImage(data: data) else { continue }
if newImages.count < 9 {
newImages.append(image)
}
}
await MainActor.run {
send(.updateProcessedImages(newImages))
}
}
case .updateProcessedImages(let images):
state.processedImages = images
return .none
case .removeImage(let index):
guard index < state.processedImages.count else { return .none }
state.processedImages.remove(at: index)
if index < state.selectedImages.count {
state.selectedImages.remove(at: index)
}
return .none
}
}
}

View File

@@ -168,66 +168,66 @@ struct CreateFeedView: View {
}
// MARK: - iOS 16+
struct ModernImageSelectionGrid: View {
let images: [UIImage]
let selectedItems: [PhotosPickerItem]
let canAddMore: Bool
let onItemsChanged: ([PhotosPickerItem]) -> Void
let onRemoveImage: (Int) -> Void
private let columns = Array(repeating: GridItem(.flexible(), spacing: 8), count: 3)
var body: some View {
WithPerceptionTracking {
LazyVGrid(columns: columns, spacing: 8) {
//
ForEach(Array(images.enumerated()), id: \.offset) { index, image in
ZStack(alignment: .topTrailing) {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(height: 100)
.clipped()
.cornerRadius(8)
//
Button(action: {
onRemoveImage(index)
}) {
Image(systemName: "xmark.circle.fill")
.font(.system(size: 20))
.foregroundColor(.white)
.background(Color.black.opacity(0.6))
.clipShape(Circle())
}
.padding(4)
}
}
//
if canAddMore {
PhotosPicker(
selection: .init(
get: { selectedItems },
set: onItemsChanged
),
maxSelectionCount: 9,
matching: .images
) {
RoundedRectangle(cornerRadius: 8)
.fill(Color.white.opacity(0.1))
.frame(height: 100)
.overlay(
Image(systemName: "plus")
.font(.system(size: 40))
.foregroundColor(.white.opacity(0.6))
)
}
}
}
}
}
}
//struct ModernImageSelectionGrid: View {
// let images: [UIImage]
// let selectedItems: [PhotosPickerItem]
// let canAddMore: Bool
// let onItemsChanged: ([PhotosPickerItem]) -> Void
// let onRemoveImage: (Int) -> Void
//
// private let columns = Array(repeating: GridItem(.flexible(), spacing: 8), count: 3)
//
// var body: some View {
// WithPerceptionTracking {
// LazyVGrid(columns: columns, spacing: 8) {
// //
// ForEach(Array(images.enumerated()), id: \.offset) { index, image in
// ZStack(alignment: .topTrailing) {
// Image(uiImage: image)
// .resizable()
// .aspectRatio(contentMode: .fill)
// .frame(height: 100)
// .clipped()
// .cornerRadius(8)
//
// //
// Button(action: {
// onRemoveImage(index)
// }) {
// Image(systemName: "xmark.circle.fill")
// .font(.system(size: 20))
// .foregroundColor(.white)
// .background(Color.black.opacity(0.6))
// .clipShape(Circle())
// }
// .padding(4)
// }
// }
//
// //
// if canAddMore {
// PhotosPicker(
// selection: .init(
// get: { selectedItems },
// set: onItemsChanged
// ),
// maxSelectionCount: 9,
// matching: .images
// ) {
// RoundedRectangle(cornerRadius: 8)
// .fill(Color.white.opacity(0.1))
// .frame(height: 100)
// .overlay(
// Image(systemName: "plus")
// .font(.system(size: 40))
// .foregroundColor(.white.opacity(0.6))
// )
// }
// }
// }
// }
// }
//}
// MARK: -
//#Preview {

View File

@@ -67,6 +67,20 @@ struct EditFeedView: View {
VStack(spacing: 0) {
headerView(geometry: geometry, viewStore: viewStore)
textInputArea(viewStore: viewStore)
//
ModernImageSelectionGrid(
images: viewStore.processedImages,
selectedItems: viewStore.selectedImages,
canAddMore: viewStore.canAddMoreImages,
onItemsChanged: { items in
viewStore.send(.photosPickerItemsChanged(items))
},
onRemoveImage: { index in
viewStore.send(.removeImage(index))
}
)
.padding(.horizontal, 24)
.padding(.bottom, 32)
Spacer()
if !isKeyboardVisible {
publishButtonBottom(viewStore: viewStore, geometry: geometry)
@@ -193,3 +207,65 @@ struct EditFeedView: View {
//#Preview {
// EditFeedView()
//}
// MARK: -
import PhotosUI
struct ModernImageSelectionGrid: View {
let images: [UIImage]
let selectedItems: [PhotosPickerItem]
let canAddMore: Bool
let onItemsChanged: ([PhotosPickerItem]) -> Void
let onRemoveImage: (Int) -> Void
private let columns = Array(repeating: GridItem(.flexible(), spacing: 8), count: 3)
var body: some View {
// gridItemSizebody
let totalSpacing: CGFloat = 8 * 2
let totalWidth = UIScreen.main.bounds.width - 24 * 2 - totalSpacing
let gridItemSize: CGFloat = totalWidth / 3
WithPerceptionTracking {
LazyVGrid(columns: columns, spacing: 8) {
ForEach(Array(images.enumerated()), id: \ .offset) { index, image in
ZStack(alignment: .topTrailing) {
Image(uiImage: image)
.resizable()
.aspectRatio(1, contentMode: .fill)
.frame(width: gridItemSize, height: gridItemSize)
.clipped()
.cornerRadius(12)
Button(action: {
onRemoveImage(index)
}) {
Image(systemName: "xmark.circle.fill")
.font(.system(size: 20))
.foregroundColor(.white)
.background(Color.black.opacity(0.6))
.clipShape(Circle())
}
.padding(4)
}
}
if canAddMore {
PhotosPicker(
selection: .init(
get: { selectedItems },
set: { items in DispatchQueue.main.async { onItemsChanged(items) } }
),
maxSelectionCount: 9 - images.count,
matching: .images
) {
RoundedRectangle(cornerRadius: 12)
.fill(Color(hexString: "1C143A"))
.frame(width: gridItemSize, height: gridItemSize)
.overlay(
Image("add photo")
.resizable()
.frame(width: 40, height: 40)
.opacity(0.6)
)
}
}
}
}
}
// gridItemSizebody
}