From 6b575dab272840eb286befecc718054096300292 Mon Sep 17 00:00:00 2001 From: edwinQQQ Date: Thu, 7 Aug 2025 11:50:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0MomentListItem?= =?UTF-8?q?=E7=82=B9=E8=B5=9E=E5=8A=9F=E8=83=BD=E5=8F=8A=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在MomentListItem中新增点赞功能,用户点击按钮可触发点赞请求。 - 使用MVVM+Combine架构管理点赞状态,确保UI与状态同步。 - 添加加载状态和错误处理,提升用户体验和交互反馈。 - 更新相关视图以支持新的点赞逻辑,优化代码可读性和维护性。 --- Debug/API response log.txt | 27 +++ Debug/debug info.txt | 51 ++++ issues/MomentListItem点赞功能实现.md | 225 ++++++++++++++++++ yana/MVVM/View/MomentListHomePage.swift | 8 +- yana/MVVM/View/MomentListItem.swift | 204 +++++++++++----- 项目问题排查与解决流程.md | 299 ------------------------ 6 files changed, 459 insertions(+), 355 deletions(-) create mode 100644 Debug/API response log.txt create mode 100644 Debug/debug info.txt create mode 100644 issues/MomentListItem点赞功能实现.md delete mode 100644 项目问题排查与解决流程.md diff --git a/Debug/API response log.txt b/Debug/API response log.txt new file mode 100644 index 0000000..a4647f8 --- /dev/null +++ b/Debug/API response log.txt @@ -0,0 +1,27 @@ +✅ [API Response] [11:19:32.208] =================== +⏱️ Duration: 0.258s +📊 Status Code: 200 +🔗 URL: https://api.epartylive.com/dynamic/like?uid=7&likedUid=563&status=1&worldId=-1&dynamicId=8 +📏 Data Size: 0 KB +📋 Response Headers: + Alt-Svc: h3=":443"; ma=2592000, h3-29=":443"; ma=2592000, h3-27=":443"; ma=2592000, h3-Q050=":443"; ma=2592000, h3-Q046=":443"; ma=2592000, h3-Q043=":443"; ma=2592000, h3-Q039=":443"; ma=2592000, quic=":443"; ma=2592000; v="39,43,46" + Content-Length: 58 + Content-Type: application/json + Date: Thu, 07 Aug 2025 03:19:34 GMT + Server: TencentEdgeOne + Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers + eo-cache-status: MISS + eo-log-uuid: 6089645366037004798 +📦 Response Data: +{ + "message" : "success", + "timestamp" : 1754536774238, + "code" : 200 +} +===================================== +🎯 [Decoded Response] [11:19:32.210] Type: LikeDynamicResponse +===================================== + +[error] ❌ MomentListItem: 点赞操作失败 +[error] 动态ID: 8 +[error] 错误: success \ No newline at end of file diff --git a/Debug/debug info.txt b/Debug/debug info.txt new file mode 100644 index 0000000..27b9ab6 --- /dev/null +++ b/Debug/debug info.txt @@ -0,0 +1,51 @@ +warning: (arm64) /Users/edwinqqq/Library/Developer/Xcode/DerivedData/yana-fuvanhpzisxarwhiosnkkltamhjw/Build/Products/Debug-iphoneos/yana.app/yana empty dSYM file detected, dSYM was created with an executable with no debug info. +[info] 🔐 Keychain 读取成功: AppLanguage +[info] 🔍 Loading items updated: 0 items +[info] 🔐 Keychain 读取成功: account_model +[info] 🔍 认证检查:认证有效 - uid: 563, ticket: eyJhbGciOi... +[info] 🎉 自动登录成功,开始获取用户信息 +[info] 🔍 认证检查:认证有效 - uid: 563, ticket: eyJhbGciOi... +[info] 🔐 Keychain 读取成功: user_info +[info] 📱 APP启动:使用现有用户信息缓存 +[info] ✅ 用户信息获取成功,进入主页 +[info] 🏗️ MainFeature 初始化 +[info] accountModel.uid: nil +[info] 转换后的uid: 0 +[info] 🔍 尝试从Keychain获取AccountModel +[info] ✅ 从Keychain获取到AccountModel: 563 +[info] meState.uid: 0 +[info] meState.displayUID: -1 +[info] meState.effectiveUID: 0 +[info] 🔍 BottomTabView get: MainFeature.Tab.feed → BottomTabView.Tab.feed +[info] 📱 MainContentView selectedTab: feed +[info] 与store.selectedTab一致: true +[info] 📱 FeedListContentView 状态: +[info] isLoading: false +[info] error: nil +[info] moments.count: 0 +[info] hasMore: true +[info] 🔍 BottomTabView get: MainFeature.Tab.feed → BottomTabView.Tab.feed +[info] 🔍 Loading items updated: 0 items +[info] 🚀 MainView onAppear +[info] 当前selectedTab: feed +[info] 📦 MainFeature: AccountModel已加载 +[info] uid: 563 +[info] 🔄 更新MeFeature状态,uid: 563 +[info] ✅ FeedListFeature: 认证信息已准备好,开始获取动态 +[info] 🏗️ MainFeature 初始化 +[info] accountModel.uid: nil +[info] 转换后的uid: 0 +[info] 🔍 尝试从Keychain获取AccountModel +[info] meState.uid: 0 +[info] meState.displayUID: -1 +[info] meState.effectiveUID: 0 +[info] ✅ 从Keychain获取到AccountModel: 563 +[info] 🔍 BottomTabView get: MainFeature.Tab.feed → BottomTabView.Tab.feed +[info] 📱 MainContentView selectedTab: feed +[info] 与store.selectedTab一致: true +[info] 📱 FeedListContentView 状态: +[info] isLoading: false +[info] error: nil +[info] moments.count: 0 +[info] hasMore: true +[info] 🔍 BottomTabView get: MainFeature.Tab.feed → BottomTabView.Tab.feed \ No newline at end of file diff --git a/issues/MomentListItem点赞功能实现.md b/issues/MomentListItem点赞功能实现.md new file mode 100644 index 0000000..48d3106 --- /dev/null +++ b/issues/MomentListItem点赞功能实现.md @@ -0,0 +1,225 @@ +# MomentListItem 点赞功能实现 (MVVM+Combine) + +## 需求分析 +1. 用户可以点击 like 按钮 +2. 点击 like 按钮时,触发 LikeDynamicRequest 请求 +3. 当 moment.isLike 为 true 时,请求的 status 参数传 0(取消点赞) +4. 当 moment.isLike 为 false 时,请求的 status 参数传 1(点赞) +5. 请求成功后,更新 MomentListItem 的 like 状态 + +## 架构选择 +**使用 MVVM+Combine 架构**,参考 MomentListHomeViewModel 的实现模式: +- 不使用 TCA 框架 +- 使用 @State 管理本地状态 +- 使用 LiveAPIService 直接发起 API 请求 +- 使用 Task 和 async/await 处理异步操作 + +## 实施计划 + +### 文件结构 +- ✅ 修改:`yana/MVVM/View/MomentListItem.swift` + +### 核心组件设计 +1. **状态管理**: + - `@State private var isLikeLoading = false` - 点赞加载状态 + - `@State private var localIsLike: Bool` - 本地点赞状态 + - `@State private var localLikeCount: Int` - 本地点赞数量 + +2. **API 请求**: + - 使用 `LiveAPIService()` 直接创建服务实例 + - 使用 `UserInfoManager.getCurrentUserId()` 获取当前用户ID + - 使用 `LikeDynamicRequest` 创建请求 + +3. **点赞处理逻辑**: + - `handleLikeTap()` - 处理点赞按钮点击 + - `performLikeRequest()` - 执行点赞 API 请求 + +### 实施步骤 +1. ✅ 移除 TCA 相关导入和依赖 +2. ✅ 添加 @State 状态变量 +3. ✅ 实现点赞按钮的点击处理 +4. ✅ 实现 API 请求逻辑(参考 MomentListHomeViewModel) +5. ✅ 更新 UI 显示状态 +6. ✅ 添加错误处理和加载状态 + +### 技术要点 +- 使用 `LiveAPIService()` 直接创建服务实例 +- 使用 `UserInfoManager.getCurrentUserId()` 获取当前用户ID +- 使用 `APILoadingManager` 显示错误信息 +- 使用 `debugInfoSync` 和 `debugErrorSync` 记录日志 +- 使用 `MainActor.run` 确保 UI 更新在主线程 + +## 实现细节 + +### 状态初始化 +```swift +init(moment: MomentsInfo, onImageTap: @escaping (([String], Int)) -> Void = { (arg) in let (_, _) = arg; }) { + self.moment = moment + self.onImageTap = onImageTap + // 初始化本地状态 + self._localIsLike = State(initialValue: moment.isLike) + self._localLikeCount = State(initialValue: moment.likeCount) +} +``` + +### 点赞按钮 UI +```swift +Button(action: { + if !isLikeLoading { + handleLikeTap() + } +}) { + HStack(spacing: 4) { + if isLikeLoading { + ProgressView() + .progressViewStyle(CircularProgressViewStyle(tint: localIsLike ? .red : .white.opacity(0.8))) + .scaleEffect(0.8) + } else { + Image(systemName: localIsLike ? "heart.fill" : "heart") + .font(.system(size: 16)) + } + Text("\(localLikeCount)") + .font(.system(size: 14)) + } + .foregroundColor(localIsLike ? .red : .white.opacity(0.8)) +} +.disabled(isLikeLoading) +``` + +### API 请求逻辑 +```swift +private func performLikeRequest() async { + // 设置加载状态 + await MainActor.run { + isLikeLoading = true + } + + do { + // 获取当前用户ID + guard let currentUserId = await UserInfoManager.getCurrentUserId(), + let currentUserIdInt = Int(currentUserId) else { + await MainActor.run { + isLikeLoading = false + } + setAPILoadingErrorSync(UUID(), errorMessage: "无法获取用户信息,请重新登录") + return + } + + // 确定请求参数 + let status = localIsLike ? 0 : 1 // 0: 取消点赞, 1: 点赞 + + // 创建 API 服务实例 + let apiService = LiveAPIService() + + // 创建请求 + let request = LikeDynamicRequest( + dynamicId: moment.dynamicId, + uid: moment.uid, + status: status, + likedUid: currentUserIdInt, + worldId: moment.worldId + ) + + debugInfoSync("📡 MomentListItem: 发送点赞请求") + debugInfoSync(" 动态ID: \(moment.dynamicId)") + debugInfoSync(" 当前状态: \(localIsLike)") + debugInfoSync(" 请求状态: \(status)") + + // 发起请求 + let response: LikeDynamicResponse = try await apiService.request(request) + + await MainActor.run { + isLikeLoading = false + + // 处理响应 + if let data = response.data, let success = data.success, success { + // 更新本地状态 + localIsLike = !localIsLike + localLikeCount = data.likeCount ?? localLikeCount + debugInfoSync("✅ MomentListItem: 点赞操作成功") + debugInfoSync(" 动态ID: \(moment.dynamicId)") + debugInfoSync(" 新状态: \(localIsLike)") + debugInfoSync(" 新数量: \(localLikeCount)") + } else { + // 显示错误信息 + let errorMessage = response.message.isEmpty ? "点赞失败,请重试" : response.message + setAPILoadingErrorSync(UUID(), errorMessage: errorMessage) + debugErrorSync("❌ MomentListItem: 点赞操作失败") + debugErrorSync(" 动态ID: \(moment.dynamicId)") + debugErrorSync(" 错误: \(errorMessage)") + } + } + + } catch { + await MainActor.run { + isLikeLoading = false + } + setAPILoadingErrorSync(UUID(), errorMessage: error.localizedDescription) + debugErrorSync("❌ MomentListItem: 点赞请求异常") + debugErrorSync(" 动态ID: \(moment.dynamicId)") + debugErrorSync(" 错误: \(error.localizedDescription)") + } +} +``` + +## 架构对比 + +### 与 TCA 架构的区别 +| 方面 | TCA 架构 | MVVM+Combine 架构 | +|------|----------|-------------------| +| 依赖注入 | @Dependency(\.apiService) | LiveAPIService() | +| 状态管理 | @ObservableState | @State | +| 异步处理 | Effect.task | Task + async/await | +| 错误处理 | 通过 Effect 处理 | 直接 try-catch | +| 复杂度 | 较高 | 较低 | + +### 与 MomentListHomeViewModel 的一致性 +- ✅ 使用相同的 API 服务创建方式 +- ✅ 使用相同的错误处理模式 +- ✅ 使用相同的日志记录方式 +- ✅ 使用相同的用户验证逻辑 + +## 功能特性 + +### 交互体验 +- **即时反馈**:点击后立即显示加载状态 +- **状态切换**:成功后在点赞/取消点赞状态间切换 +- **数量更新**:实时更新点赞数量显示 +- **错误处理**:网络错误或服务器错误时显示友好提示 + +### 状态管理 +- **本地状态**:使用 `@State` 管理本地点赞状态,避免影响其他组件 +- **加载状态**:防止重复点击,提供视觉反馈 +- **错误恢复**:请求失败时保持原有状态 + +### 安全性 +- **用户验证**:确保用户已登录才能点赞 +- **参数验证**:正确传递点赞状态参数 +- **错误边界**:完善的错误处理机制 + +## 测试要点 +1. 点赞状态切换正确(true → false, false → true) +2. 点赞数量实时更新 +3. 加载状态显示正常 +4. 网络错误处理正确 +5. 用户未登录时的错误提示 +6. 重复点击防护 +7. 与其他组件的状态同步 + +## 完成状态 +- [x] 移除 TCA 相关代码 +- [x] 实现 MVVM+Combine 架构 +- [x] 实现状态管理 +- [x] 实现点赞按钮 UI +- [x] 实现 API 请求逻辑 +- [x] 实现错误处理 +- [x] 实现加载状态 +- [x] 添加日志记录 +- [x] 代码审查和优化 + +## 注意事项 +1. 本实现使用本地状态管理,不会影响其他使用相同动态数据的组件 +2. 如果需要全局状态同步,建议在父组件中实现状态管理 +3. 点赞操作是幂等的,重复请求不会产生副作用 +4. 错误处理使用全局的 APILoadingManager,确保用户体验一致 +5. 架构选择符合项目要求,不使用 TCA 框架 diff --git a/yana/MVVM/View/MomentListHomePage.swift b/yana/MVVM/View/MomentListHomePage.swift index 5ecb44a..b688c00 100644 --- a/yana/MVVM/View/MomentListHomePage.swift +++ b/yana/MVVM/View/MomentListHomePage.swift @@ -40,7 +40,8 @@ struct MomentListHomePage: View { .padding(.top, 16) // 标语 - Text(LocalizedString("feedList.slogan", comment: "The disease is like a cruel ruler,\nand time is our most precious treasure.\nEvery moment we live is a victory\nagainst the inevitable.")) + Text(LocalizedString("feedList.slogan", + comment: "")) .font(.system(size: 16)) .multilineTextAlignment(.leading) .foregroundColor(.white.opacity(0.9)) @@ -64,7 +65,8 @@ struct MomentListHomePage: View { debugInfoSync(" 图片数量: \(images.count)") } ) - .padding(.horizontal, 16) + .padding(.leading, 16) + .padding(.trailing, 32) .onAppear { // 当显示倒数第三个项目时,开始加载更多 if index == viewModel.moments.count - 3 { @@ -94,7 +96,7 @@ struct MomentListHomePage: View { .padding(.vertical, 20) } } - .padding(.bottom, 100) // 为底部导航栏留出空间 + .padding(.bottom, 160) // 为底部导航栏留出空间 } .refreshable { // 下拉刷新 diff --git a/yana/MVVM/View/MomentListItem.swift b/yana/MVVM/View/MomentListItem.swift index 8e517ac..a25f1ac 100644 --- a/yana/MVVM/View/MomentListItem.swift +++ b/yana/MVVM/View/MomentListItem.swift @@ -5,12 +5,20 @@ struct MomentListItem: View { let moment: MomentsInfo let onImageTap: (([String], Int)) -> Void // 新增:图片点击回调 + // 新增:点赞相关状态 + @State private var isLikeLoading = false + @State private var localIsLike: Bool + @State private var localLikeCount: Int + init( moment: MomentsInfo, onImageTap: @escaping (([String], Int)) -> Void = { (arg) in let (_, _) = arg; } ) { self.moment = moment self.onImageTap = onImageTap + // 初始化本地状态 + self._localIsLike = State(initialValue: moment.isLike) + self._localLikeCount = State(initialValue: moment.likeCount) } var body: some View { @@ -84,13 +92,26 @@ struct MomentListItem: View { // 互动按钮 HStack(spacing: 20) { // Like 按钮与用户名左侧对齐 - HStack(spacing: 4) { - Image(systemName: moment.isLike ? "heart.fill" : "heart") - .font(.system(size: 16)) - Text("\(moment.likeCount)") - .font(.system(size: 14)) + Button(action: { + if !isLikeLoading { + handleLikeTap() + } + }) { + HStack(spacing: 4) { + if isLikeLoading { + ProgressView() + .progressViewStyle(CircularProgressViewStyle(tint: localIsLike ? .red : .white.opacity(0.8))) + .scaleEffect(0.8) + } else { + Image(systemName: localIsLike ? "heart.fill" : "heart") + .font(.system(size: 16)) + } + Text("\(localLikeCount)") + .font(.system(size: 14)) + } + .foregroundColor(localIsLike ? .red : .white.opacity(0.8)) } - .foregroundColor(moment.isLike ? .red : .white.opacity(0.8)) + .disabled(isLikeLoading) .padding(.leading, 40 + 8) // 与用户名左侧对齐(头像宽度 + 间距) Spacer() } @@ -100,6 +121,83 @@ struct MomentListItem: View { } } + // MARK: - 点赞处理逻辑 + private func handleLikeTap() { + Task { + await performLikeRequest() + } + } + + private func performLikeRequest() async { + // 设置加载状态 + await MainActor.run { + isLikeLoading = true + } + + do { + // 获取当前用户ID + guard let currentUserId = await UserInfoManager.getCurrentUserId(), + let currentUserIdInt = Int(currentUserId) else { + await MainActor.run { + isLikeLoading = false + } + setAPILoadingErrorSync(UUID(), errorMessage: "无法获取用户信息,请重新登录") + return + } + + // 确定请求参数 + let status = localIsLike ? 0 : 1 // 0: 取消点赞, 1: 点赞 + + // 创建 API 服务实例 + let apiService = LiveAPIService() + + // 创建请求 + let request = LikeDynamicRequest( + dynamicId: moment.dynamicId, + uid: currentUserIdInt, + status: status, + likedUid: moment.uid, + worldId: moment.worldId + ) + + debugInfoSync("📡 MomentListItem: 发送点赞请求") + debugInfoSync(" 动态ID: \(moment.dynamicId)") + debugInfoSync(" 当前状态: \(localIsLike)") + debugInfoSync(" 请求状态: \(status)") + + // 发起请求 + let response: LikeDynamicResponse = try await apiService.request(request) + + await MainActor.run { + isLikeLoading = false + // 处理响应, 只需要判断 code + if response.code == 200 { + localIsLike = !localIsLike + localLikeCount = localIsLike ? localLikeCount+1 : localLikeCount-1 + debugInfoSync("✅ MomentListItem: 点赞操作成功") + debugInfoSync(" 动态ID: \(moment.dynamicId)") + debugInfoSync(" 新状态: \(localIsLike)") + debugInfoSync(" 新数量: \(localLikeCount)") + } else { + let errorMessage = response.message.isEmpty ? "点赞失败,请重试" : response.message + setAPILoadingErrorSync(UUID(), errorMessage: errorMessage) + debugErrorSync("❌ MomentListItem: 点赞操作失败") + debugErrorSync(" 动态ID: \(moment.dynamicId)") + debugErrorSync(" 错误: \(errorMessage)") + } + } + + } catch { + await MainActor.run { + isLikeLoading = false + } + setAPILoadingErrorSync(UUID(), errorMessage: error.localizedDescription) + debugErrorSync("❌ MomentListItem: 点赞请求异常") + debugErrorSync(" 动态ID: \(moment.dynamicId)") + debugErrorSync(" 错误: \(error.localizedDescription)") + } + } + // MARK: - 时间显示逻辑 private func formatDisplayTime(_ timestamp: Int) -> String { let date = Date(timeIntervalSince1970: TimeInterval(timestamp) / 1000.0) @@ -251,50 +349,50 @@ struct MomentSquareImageView: View { } } -#Preview { - // 创建测试数据 - let testMoment = MomentsInfo( - dynamicId: 1, - uid: 123456, - nick: "测试用户", - avatar: "", - type: 0, - content: "这是一条测试动态内容,用来测试 MomentListItem 的显示效果。", - likeCount: 42, - isLike: false, - commentCount: 5, - publishTime: Int(Date().timeIntervalSince1970 * 1000), - worldId: 1, - status: 1, - playCount: nil, - dynamicResList: [ - MomentsPicture(id: 1, resUrl: "https://picsum.photos/300/300", format: "jpg", width: 300, height: 300, resDuration: nil), - MomentsPicture(id: 2, resUrl: "https://picsum.photos/301/301", format: "jpg", width: 301, height: 301, resDuration: nil) - ], - gender: nil, - squareTop: nil, - topicTop: nil, - newUser: nil, - defUser: nil, - scene: nil, - userVipInfoVO: nil, - headwearPic: nil, - headwearEffect: nil, - headwearType: nil, - headwearName: nil, - headwearId: nil, - experLevelPic: nil, - charmLevelPic: nil, - isCustomWord: nil, - labelList: nil - ) - - MomentListItem( - moment: testMoment, - onImageTap: { images, index in - print("图片被点击: 索引 \(index), 图片数量 \(images.count)") - } - ) - .padding() - .background(Color.black) -} +//#Preview { +// // 创建测试数据 +// let testMoment = MomentsInfo( +// dynamicId: 1, +// uid: 123456, +// nick: "测试用户", +// avatar: "", +// type: 0, +// content: "这是一条测试动态内容,用来测试 MomentListItem 的显示效果。", +// likeCount: 42, +// isLike: false, +// commentCount: 5, +// publishTime: Int(Date().timeIntervalSince1970 * 1000), +// worldId: 1, +// status: 1, +// playCount: nil, +// dynamicResList: [ +// MomentsPicture(id: 1, resUrl: "https://picsum.photos/300/300", format: "jpg", width: 300, height: 300, resDuration: nil), +// MomentsPicture(id: 2, resUrl: "https://picsum.photos/301/301", format: "jpg", width: 301, height: 301, resDuration: nil) +// ], +// gender: nil, +// squareTop: nil, +// topicTop: nil, +// newUser: nil, +// defUser: nil, +// scene: nil, +// userVipInfoVO: nil, +// headwearPic: nil, +// headwearEffect: nil, +// headwearType: nil, +// headwearName: nil, +// headwearId: nil, +// experLevelPic: nil, +// charmLevelPic: nil, +// isCustomWord: nil, +// labelList: nil +// ) +// +// MomentListItem( +// moment: testMoment, +// onImageTap: { images, index in +// print("图片被点击: 索引 \(index), 图片数量 \(images.count)") +// } +// ) +// .padding() +// .background(Color.black) +//} diff --git a/项目问题排查与解决流程.md b/项目问题排查与解决流程.md deleted file mode 100644 index dda9cb8..0000000 --- a/项目问题排查与解决流程.md +++ /dev/null @@ -1,299 +0,0 @@ -# Yana 项目问题排查与解决流程文档 - -## 目录 - -1. [问题概述](#问题概述) -2. [解决流程](#解决流程) -3. [技术细节](#技术细节) -4. [最终解决方案](#最终解决方案) -5. [预防措施](#预防措施) -6. [常见问题FAQ](#常见问题faq) - ---- - -## 问题概述 - -### 初始错误 - -**错误信息**: `"Could not compute dependency graph: unable to load transferred PIF: The workspace contains multiple references with the same GUID"` - -**问题表现**: - -- 项目无法启动 -- Xcode 无法计算依赖图 -- 出现 GUID 冲突错误 - -### 根本原因分析 - -1. **混合包管理系统**: 项目同时使用了 Swift Package Manager (SPM) 和 CocoaPods -2. **缓存冲突**: Xcode DerivedData 与 SPM 状态不同步 -3. **TCA 结构问题**: 代码中 HomeFeature 缺少必要的状态和 Action 定义 - ---- - -## 解决流程 - -### 第一阶段:GUID 冲突解决 - -#### 步骤 1: 清理缓存 - -```bash -# 清理 Xcode DerivedData -rm -rf ~/Library/Developer/Xcode/DerivedData/* - -# 重置 Swift Package Manager -swift package reset -swift package resolve -``` - -#### 步骤 2: 重新安装 CocoaPods - -```bash -pod install --clean-install -``` - -#### 步骤 3: 验证项目解析 - -```bash -xcodebuild -workspace yana.xcworkspace -list -``` - -### 第二阶段:TCA 结构修复 - -#### 问题识别 - -- `HomeFeature.State` 缺少 `isSettingPresented` 和 `settingState` 属性 -- `HomeFeature.Action` 缺少 `settingDismissed` 和 `setting` actions -- `HomeView.swift` 中的 `store.scope()` 调用语法错误 - -#### 修复步骤 - -1. 修复 HomeFeature.swift - -```swift -@ObservableState -struct State: Equatable { - var isInitialized = false - var userInfo: UserInfo? - var accountModel: AccountModel? - var error: String? - - // 添加设置页面相关状态 - var isSettingPresented = false - var settingState = SettingFeature.State() -} - -enum Action: Equatable { - case onAppear - case loadUserInfo - case userInfoLoaded(UserInfo?) - case loadAccountModel - case accountModelLoaded(AccountModel?) - case logoutTapped - case logout - - // 添加设置页面相关actions - case settingDismissed - case setting(SettingFeature.Action) -} -``` - -2.添加子 Reducer - -```swift -var body: some ReducerOf { - Scope(state: \.settingState, action: \.setting) { - SettingFeature() - } - - Reduce { state, action in - // ... existing cases ... - - case .settingDismissed: - state.isSettingPresented = false - return .none - - case .setting: - // 由子reducer处理 - return .none - } -} -``` - -3.修复 HomeView.swift - -```swift -.sheet(isPresented: Binding( - get: { store.isSettingPresented }, - set: { _ in store.send(.settingDismissed) } -)) { - SettingView(store: store.scope(state: \.settingState, action: \.setting)) -} -``` - ---- - -## 技术细节 - -### 依赖管理配置 - -**Swift Package Manager (Package.swift)**: - -- ComposableArchitecture: 1.20.2+ -- 其他依赖根据需要添加 - -**CocoaPods (Podfile)**: - -- Alamofire (网络请求) -- SDWebImage (图像加载) -- CocoaLumberjack (日志) -- 其他 UI 相关库 - -### TCA 架构模式 - -``` -Feature -├── State (数据状态) -├── Action (用户操作) -├── Reducer (状态转换逻辑) -└── Dependencies (外部依赖) -``` - -### 文件结构 - -``` -yana/ -├── Features/ # TCA Feature 定义 -├── Views/ # SwiftUI 视图 -├── APIs/ # 网络 API 层 -├── Utils/ # 工具类 -└── Managers/ # 管理器类 -``` - ---- - -## 最终解决方案 - -### 命令执行顺序 - -```bash -# 1. 清理环境 -rm -rf ~/Library/Developer/Xcode/DerivedData/* -swift package reset - -# 2. 重新解析依赖 -swift package resolve -pod install --clean-install - -# 3. 验证项目 -xcodebuild -workspace yana.xcworkspace -scheme yana -configuration Debug build -``` - -### 关键代码修改 - -1. **HomeFeature.swift**: 添加设置相关状态管理 -2. **HomeView.swift**: 修复 TCA store 绑定语法 -3. **SettingFeature.swift**: 确保 Action 完整性 - -### 构建结果 - -✅ **编译成功**: Exit code 0 -⚠️ **警告信息**: 仅 Swift 6 兼容性警告,不影响运行 - ---- - -## 预防措施 - -### 开发规范 - -1. **统一包管理**: 优先使用一种包管理工具 -2. **定期清理**: 定期清理 DerivedData 避免缓存问题 -3. **代码审查**: 确保 TCA Feature 结构完整 -4. **版本控制**: 及时提交关键配置文件 - -### 监控指标 - -- [ ] 项目编译时间 < 30s -- [ ] 无编译错误 -- [ ] 依赖解析正常 -- [ ] TCA 结构完整 - -### 工具使用 - -```bash -# 项目健康检查脚本 -check_project() { - echo "🔍 检查项目状态..." - xcodebuild -workspace yana.xcworkspace -list > /dev/null 2>&1 - if [ $? -eq 0 ]; then - echo "✅ 项目解析正常" - else - echo "❌ 项目解析失败,需要执行清理流程" - return 1 - fi -} -``` - ---- - -## 常见问题FAQ - -### Q1: 再次出现 GUID 冲突怎么办? - -**A**: 执行完整清理流程: - ```bash - rm -rf ~/Library/Developer/Xcode/DerivedData/* - swift package reset && swift package resolve - pod install --clean-install - ``` - -### Q2: TCA Reducer 编译错误如何处理? - -**A**: 检查以下项目: - -- State 属性完整性 -- Action 枚举完整性 -- Reducer body 中的 case 处理 -- 子 Reducer 的 Scope 配置 - -### Q3: 如何避免混合包管理器问题? - -**A**: - -- 尽量使用单一包管理工具 -- 如需混合使用,确保依赖版本兼容 -- 定期更新依赖并测试 - -### Q4: Swift 6 兼容性警告如何处理? - -**A**: - -- 短期:可以忽略,不影响功能 -- 长期:逐步迁移到 Swift 6 Sendable 模式 - -### Q5: 项目构建缓慢怎么办? - -**A**: - -- 使用 `xcodebuild -quiet` 减少输出 -- 开启 Xcode Build System 并行构建 -- 定期清理 DerivedData - ---- - -## 总结 - -本次问题解决涉及以下关键技术点: - -1. **Xcode 项目配置管理** -2. **Swift Package Manager 与 CocoaPods 共存** -3. **TCA (The Composable Architecture) 最佳实践** -4. **iOS 开发环境故障排除** - -通过系统性的排查和修复,项目现已恢复正常运行状态。建议团队建立定期维护机制,避免类似问题再次发生。 - ---- - -**文档更新时间**: 2025-07-10 -**适用版本**: iOS 17+, Swift 6, TCA 1.20.2+ -**维护者**: AI Assistant & 开发团队