feat: 增强编辑动态功能,支持图片选择与处理
- 在EditFeedFeature中新增图片选择相关状态和动作,支持用户选择和处理最多9张图片。 - 在EditFeedView中集成ModernImageSelectionGrid组件,优化图片选择界面。 - 更新CreateFeedView以支持图片选择功能,提升用户体验。 - 实现图片处理逻辑,确保用户能够方便地添加和删除图片。
This commit is contained in:
@@ -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
|
||||
}
|
||||
// 手动实现Equatable,selectedImages只比较数量(PhotosPickerItem不支持Equatable)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
// 将gridItemSize移入body内部变量
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 移除gridItemSize属性,全部逻辑已移入body
|
||||
}
|
||||
|
Reference in New Issue
Block a user