
- 将SplashV2替换为SplashPage,优化应用启动流程。 - 在LoginModels中将用户ID参数更改为加密后的ID,增强安全性。 - 更新AppConfig中的API基础URL,确保与生产环境一致。 - 在CommonComponents中添加底部Tab栏图标映射逻辑,提升用户体验。 - 新增NineGridImagePicker组件,支持多图选择功能,优化CreateFeedPage的图片选择逻辑。 - 移除冗余的BottomTabView组件,简化视图结构,提升代码整洁性。 - 在MainPage中整合创建动态页面的逻辑,优化导航体验。 - 新增MePage视图,提供用户信息管理功能,增强应用的可用性。
124 lines
5.4 KiB
Swift
124 lines
5.4 KiB
Swift
import SwiftUI
|
||
import PhotosUI
|
||
|
||
struct NineGridImagePicker: View {
|
||
@Binding var images: [UIImage]
|
||
var maxCount: Int = 9
|
||
var cornerRadius: CGFloat = 16
|
||
var spacing: CGFloat = 8
|
||
var horizontalPadding: CGFloat = 20
|
||
var onTapImage: (Int) -> Void = { _ in }
|
||
|
||
@State private var pickerItems: [PhotosPickerItem] = []
|
||
|
||
var body: some View {
|
||
GeometryReader { geometry in
|
||
let columns = Array(repeating: GridItem(.flexible(), spacing: spacing), count: 3)
|
||
let columnsCount: CGFloat = 3
|
||
let totalSpacing = spacing * (columnsCount - 1)
|
||
let availableWidth = geometry.size.width - horizontalPadding * 2
|
||
let cellSide = (availableWidth - totalSpacing) / columnsCount
|
||
|
||
LazyVGrid(columns: columns, spacing: spacing) {
|
||
ForEach(0..<maxCount, id: \.self) { index in
|
||
ZStack {
|
||
// 占位背景(仅 DEBUG 可见)
|
||
#if DEBUG
|
||
if index >= images.count && !(index == images.count && images.count < maxCount) {
|
||
RoundedRectangle(cornerRadius: cornerRadius)
|
||
.fill(Color.white.opacity(0.08))
|
||
}
|
||
#endif
|
||
|
||
if index < images.count {
|
||
// 图片格子
|
||
ZStack(alignment: .topTrailing) {
|
||
Image(uiImage: images[index])
|
||
.resizable()
|
||
.scaledToFill()
|
||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||
.contentShape(RoundedRectangle(cornerRadius: cornerRadius))
|
||
.onTapGesture { onTapImage(index) }
|
||
|
||
Button {
|
||
removeImage(at: index)
|
||
} label: {
|
||
Image(systemName: "xmark.circle.fill")
|
||
.foregroundColor(.white)
|
||
.background(Circle().fill(Color.black.opacity(0.4)))
|
||
.font(.system(size: 16, weight: .bold))
|
||
}
|
||
.padding(6)
|
||
.buttonStyle(.plain)
|
||
}
|
||
} else if index == images.count && images.count < maxCount {
|
||
// 添加按钮格子
|
||
PhotosPicker(
|
||
selection: $pickerItems,
|
||
maxSelectionCount: maxCount - images.count,
|
||
selectionBehavior: .ordered,
|
||
matching: .images
|
||
) {
|
||
ZStack {
|
||
RoundedRectangle(cornerRadius: cornerRadius)
|
||
.fill(Color(hex: 0x1C143A))
|
||
Image(systemName: "plus")
|
||
.foregroundColor(.white.opacity(0.6))
|
||
.font(.system(size: 32, weight: .semibold))
|
||
}
|
||
}
|
||
.onChange(of: pickerItems) { _, newItems in
|
||
handlePickerItems(newItems)
|
||
}
|
||
}
|
||
}
|
||
.frame(height: cellSide)
|
||
.clipShape(RoundedRectangle(cornerRadius: cornerRadius))
|
||
.contentShape(RoundedRectangle(cornerRadius: cornerRadius))
|
||
}
|
||
}
|
||
.padding(.horizontal, horizontalPadding)
|
||
}
|
||
.frame(height: gridHeight(forCount: max(images.count, 1)))
|
||
}
|
||
|
||
private func gridHeight(forCount count: Int) -> CGFloat {
|
||
// 通过一个近似:用屏幕宽度估算高度以确保父布局正确测量。
|
||
// 每行 3 个,行数 = ceil(count / 3.0)。在 GeometryReader 中真实高度会覆盖此近似。
|
||
let screenWidth = UIScreen.main.bounds.width
|
||
let columnsCount: CGFloat = 3
|
||
let totalSpacing = spacing * (columnsCount - 1)
|
||
let availableWidth = screenWidth - horizontalPadding * 2
|
||
let side = (availableWidth - totalSpacing) / columnsCount
|
||
let rows = ceil(CGFloat(count) / 3.0)
|
||
let totalRowSpacing = spacing * max(rows - 1, 0)
|
||
return side * rows + totalRowSpacing
|
||
}
|
||
|
||
private func handlePickerItems(_ items: [PhotosPickerItem]) {
|
||
guard !items.isEmpty else { return }
|
||
Task { @MainActor in
|
||
var appended: [UIImage] = []
|
||
for item in items {
|
||
if images.count + appended.count >= maxCount { break }
|
||
if let data = try? await item.loadTransferable(type: Data.self),
|
||
let image = UIImage(data: data) {
|
||
appended.append(image)
|
||
}
|
||
}
|
||
if !appended.isEmpty {
|
||
images.append(contentsOf: appended)
|
||
}
|
||
pickerItems = []
|
||
}
|
||
}
|
||
|
||
private func removeImage(at index: Int) {
|
||
guard images.indices.contains(index) else { return }
|
||
images.remove(at: index)
|
||
}
|
||
}
|
||
|
||
|
||
|