From 1f98ed534d654b20f382372fed78d908479715d0 Mon Sep 17 00:00:00 2001 From: edwinQQQ Date: Mon, 14 Jul 2025 14:50:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0Swift=E5=8A=A9?= =?UTF-8?q?=E6=89=8B=E6=A0=B7=E5=BC=8F=E5=92=8C=E5=8A=A8=E6=80=81=E8=A7=86?= =?UTF-8?q?=E5=9B=BE=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在swift-assistant-style.mdc中更新上下文信息,简化描述并保留关键信息。 - 在swift-swiftui-dev-rules.mdc中将alwaysApply设置为false,调整开发规则。 - 在Info.plist中移除冗余的CFBundleDisplayName和CFBundleName键,保持文件整洁。 - 在FeedFeature.swift中添加调试日志,增强API响应的可追踪性。 - 在HomeFeature.swift中新增Feed状态和相关actions,优化状态管理。 - 在FeedView.swift中直接使用store状态,提升组件性能和可读性。 - 在HomeView.swift中更新FeedView的store传递方式,确保状态一致性。 - 更新Xcode项目配置,调整代码签名和Swift版本,确保兼容性。 --- .cursor/rules/swift-assistant-style.mdc | 41 +----- .cursor/rules/swift-swiftui-dev-rules.mdc | 4 +- yana.xcodeproj/project.pbxproj | 24 +-- .../xcshareddata/xcschemes/yana.xcscheme | 91 ++++++++++++ .../xcschemes/xcschememanagement.plist | 13 ++ .../xcdebugger/Breakpoints_v2.xcbkptlist | 34 ----- yana/Features/FeedFeature.swift | 20 ++- yana/Features/HomeFeature.swift | 18 ++- yana/Info.plist | 4 - yana/Views/FeedView.swift | 138 +++++++++--------- yana/Views/HomeView.swift | 4 +- 11 files changed, 232 insertions(+), 159 deletions(-) create mode 100644 yana.xcodeproj/xcshareddata/xcschemes/yana.xcscheme diff --git a/.cursor/rules/swift-assistant-style.mdc b/.cursor/rules/swift-assistant-style.mdc index d00ac0b..dfc3b9e 100644 --- a/.cursor/rules/swift-assistant-style.mdc +++ b/.cursor/rules/swift-assistant-style.mdc @@ -3,56 +3,30 @@ description: globs: alwaysApply: true --- +# CONTEXT + I wish to receive advice using the latest tools and seek step-by-step guidance to fully understand the implementation process. - # CONTEXT - - I am a native Chinese speaker who has just begun learning Swift 6 and Xcode 15+, and I am enthusiastic about exploring new technologies. I wish to receive advice using the latest tools and - seek step-by-step guidance to fully understand the implementation process. Since many excellent code resources are in English, I hope my questions can be thoroughly understood. Therefore, - I would like the AI assistant to think and reason in English, then translate the English responses into Chinese for me. - - --- - # OBJECTIVE - As an expert AI programming assistant, your task is to provide me with clear and readable SwiftUI code. You should: - - Utilize the latest versions of SwiftUI and Swift, being familiar with the newest features and best practices. - Provide careful and accurate answers that are well-founded and thoughtfully considered. - **Explicitly use the Chain-of-Thought (CoT) method in your reasoning and answers, explaining your thought process step by step.** - Strictly adhere to my requirements and meticulously complete the tasks. - Begin by outlining your proposed approach with detailed steps or pseudocode. - Upon confirming the plan, proceed to write the code. - - --- - + # STYLE - - Keep answers concise and direct, minimizing unnecessary wording. - Emphasize code readability over performance optimization. - Maintain a professional and supportive tone, ensuring clarity of content. - --- - - # TONE - - - Be positive and encouraging, helping me improve my programming skills. - - Be professional and patient, assisting me in understanding each step. - - --- # AUDIENCE - - The target audience is me—a native Chinese developer eager to learn Swift 6 and Xcode 15+, seeking guidance and advice on utilizing the latest technologies. - - --- + The target audience is me, a native Chinese developer eager to learn Swift 6 and Xcode 15.9, seeking guidance and advice on utilizing the latest technologies. # RESPONSE FORMAT - - **Utilize the Chain-of-Thought (CoT) method to reason and respond, explaining your thought process step by step.** - - Conduct reasoning, thinking, and code writing in English. - - The final reply should translate the English into Chinese for me. - The reply should include: - 1. **Step-by-Step Plan**: Describe the implementation process with detailed pseudocode or step-by-step explanations, showcasing your thought process. 2. **Code Implementation**: Provide correct, up-to-date, error-free, fully functional, runnable, secure, and efficient code. The code should: - Include all necessary imports and properly name key components. @@ -60,10 +34,3 @@ alwaysApply: true 3. **Concise Response**: Minimize unnecessary verbosity, focusing only on essential information. - If a correct answer may not exist, please point it out. If you do not know the answer, please honestly inform me rather than guessing. - - --- - - # START ANALYSIS - - If you understand, please prepare to assist me and await my question. - \ No newline at end of file diff --git a/.cursor/rules/swift-swiftui-dev-rules.mdc b/.cursor/rules/swift-swiftui-dev-rules.mdc index 915f72b..1a8190c 100644 --- a/.cursor/rules/swift-swiftui-dev-rules.mdc +++ b/.cursor/rules/swift-swiftui-dev-rules.mdc @@ -1,7 +1,5 @@ --- -description: -globs: -alwaysApply: true +alwaysApply: false --- You are an expert iOS developer using Swift and SwiftUI. Follow these guidelines: diff --git a/yana.xcodeproj/project.pbxproj b/yana.xcodeproj/project.pbxproj index 4ab69b9..6ebf229 100644 --- a/yana.xcodeproj/project.pbxproj +++ b/yana.xcodeproj/project.pbxproj @@ -439,9 +439,10 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = yana/yana.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = EKM7RAGNA6; + DEVELOPMENT_TEAM = Z7UCRF23F3; ENABLE_PREVIEWS = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; @@ -458,7 +459,7 @@ "\"${PODS_CONFIGURATION_BUILD_DIR}/libwebp/libwebp.framework/Headers\"", ); INFOPLIST_FILE = yana/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = EParti; + INFOPLIST_KEY_CFBundleDisplayName = "E-PARTi"; INFOPLIST_KEY_NSLocalNetworkUsageDescription = "此App将可发现和连接到您所用网络上的设备。"; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "“eparty”需要您的同意,才可以进行定位服务,访问网络状态"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; @@ -473,15 +474,16 @@ ); MARKETING_VERSION = 1.0.0; OTHER_LIBTOOLFLAGS = "-framework \"SystemConfiguration\""; - PRODUCT_BUNDLE_IDENTIFIER = com.stupidmonkey.yana.yana; + PRODUCT_BUNDLE_IDENTIFIER = com.junpeiqi.eparty; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = "yana/yana-Bridging-Header.h"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 5.9; TARGETED_DEVICE_FAMILY = 1; }; name = Debug; @@ -494,9 +496,10 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = yana/yana.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = EKM7RAGNA6; + DEVELOPMENT_TEAM = Z7UCRF23F3; ENABLE_PREVIEWS = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; @@ -513,7 +516,7 @@ "\"${PODS_CONFIGURATION_BUILD_DIR}/libwebp/libwebp.framework/Headers\"", ); INFOPLIST_FILE = yana/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = EParti; + INFOPLIST_KEY_CFBundleDisplayName = "E-PARTi"; INFOPLIST_KEY_NSLocalNetworkUsageDescription = "此App将可发现和连接到您所用网络上的设备。"; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "“eparty”需要您的同意,才可以进行定位服务,访问网络状态"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; @@ -528,15 +531,16 @@ ); MARKETING_VERSION = 1.0.0; OTHER_LIBTOOLFLAGS = "-framework \"SystemConfiguration\""; - PRODUCT_BUNDLE_IDENTIFIER = com.stupidmonkey.yana.yana; + PRODUCT_BUNDLE_IDENTIFIER = com.junpeiqi.eparty; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = "yana/yana-Bridging-Header.h"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 5.9; TARGETED_DEVICE_FAMILY = 1; }; name = Release; @@ -558,7 +562,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 5.9; TARGETED_DEVICE_FAMILY = 1; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/yana.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/yana"; }; @@ -581,7 +585,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 5.9; TARGETED_DEVICE_FAMILY = 1; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/yana.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/yana"; }; diff --git a/yana.xcodeproj/xcshareddata/xcschemes/yana.xcscheme b/yana.xcodeproj/xcshareddata/xcschemes/yana.xcscheme new file mode 100644 index 0000000..15be3d7 --- /dev/null +++ b/yana.xcodeproj/xcshareddata/xcschemes/yana.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/yana.xcodeproj/xcuserdata/edwinqqq.xcuserdatad/xcschemes/xcschememanagement.plist b/yana.xcodeproj/xcuserdata/edwinqqq.xcuserdatad/xcschemes/xcschememanagement.plist index 9674f9b..26b8f97 100644 --- a/yana.xcodeproj/xcuserdata/edwinqqq.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/yana.xcodeproj/xcuserdata/edwinqqq.xcuserdatad/xcschemes/xcschememanagement.plist @@ -10,5 +10,18 @@ 3 + SuppressBuildableAutocreation + + 4C3E651E2DB61F7A00E5A455 + + primary + + + 4C4C8FBC2DE5AF9200384527 + + primary + + + diff --git a/yana.xcworkspace/xcuserdata/edwinqqq.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/yana.xcworkspace/xcuserdata/edwinqqq.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 3922ed9..0f8d13c 100644 --- a/yana.xcworkspace/xcuserdata/edwinqqq.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/yana.xcworkspace/xcuserdata/edwinqqq.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -3,38 +3,4 @@ uuid = "A60FAB2A-3184-45B2-920F-A3D7A086CF95" type = "0" version = "2.0"> - - - - - - - - - - diff --git a/yana/Features/FeedFeature.swift b/yana/Features/FeedFeature.swift index 29fe759..a7f8f41 100644 --- a/yana/Features/FeedFeature.swift +++ b/yana/Features/FeedFeature.swift @@ -74,32 +74,50 @@ struct FeedFeature { case let .momentsResponse(.success(response)): state.isLoading = false + // 添加调试日志 + debugInfo("📱 FeedFeature: API 响应成功") + debugInfo("📱 FeedFeature: response.code = \(response.code)") + debugInfo("📱 FeedFeature: response.message = \(response.message)") + debugInfo("📱 FeedFeature: response.data = \(response.data != nil ? "有数据" : "无数据")") + // 检查响应状态 guard response.code == 200, let data = response.data else { - state.error = response.message.isEmpty ? "获取动态失败" : response.message + let errorMsg = response.message.isEmpty ? "获取动态失败" : response.message + state.error = errorMsg + debugError("❌ FeedFeature: API 响应失败 - code: \(response.code), message: \(errorMsg)") return .none } + // 添加数据调试日志 + debugInfo("📱 FeedFeature: data.dynamicList.count = \(data.dynamicList.count)") + debugInfo("📱 FeedFeature: data.nextDynamicId = \(data.nextDynamicId)") + // 判断是刷新还是加载更多 let isRefresh = state.nextDynamicId == 0 + debugInfo("📱 FeedFeature: isRefresh = \(isRefresh)") if isRefresh { // 刷新:替换所有数据 state.moments = data.dynamicList + debugInfo(" FeedFeature: 刷新数据,moments.count = \(state.moments.count)") } else { // 加载更多:追加到现有数据 + let oldCount = state.moments.count state.moments.append(contentsOf: data.dynamicList) + debugInfo(" FeedFeature: 加载更多,moments.count: \(oldCount) -> \(state.moments.count)") } // 更新分页状态 state.nextDynamicId = data.nextDynamicId state.hasMoreData = !data.dynamicList.isEmpty + debugInfo("📱 FeedFeature: 更新完成 - nextDynamicId: \(state.nextDynamicId), hasMoreData: \(state.hasMoreData)") return .none case let .momentsResponse(.failure(error)): state.isLoading = false state.error = error.localizedDescription + debugError("❌ FeedFeature: API 请求失败 - \(error.localizedDescription)") return .none case .clearError: diff --git a/yana/Features/HomeFeature.swift b/yana/Features/HomeFeature.swift index 663d16a..e57f4aa 100644 --- a/yana/Features/HomeFeature.swift +++ b/yana/Features/HomeFeature.swift @@ -13,6 +13,9 @@ struct HomeFeature { // 设置页面相关状态 var isSettingPresented = false var settingState = SettingFeature.State() + + // 新增:Feed 状态 + var feedState = FeedFeature.State() } enum Action: Equatable { @@ -27,13 +30,21 @@ struct HomeFeature { // 设置页面相关actions case settingDismissed case setting(SettingFeature.Action) + + // 新增:Feed actions + case feed(FeedFeature.Action) } var body: some ReducerOf { - Scope(state: \.settingState, action: \.setting) { + Scope(state: \ .settingState, action: \ .setting) { SettingFeature() } + // 新增:Feed Scope + Scope(state: \ .feedState, action: \ .feed) { + FeedFeature() + } + Reduce { state, action in switch action { case .onAppear: @@ -79,6 +90,9 @@ struct HomeFeature { case .setting: // 由子reducer处理 return .none + case .feed(_): + // FeedFeature 的 action 已由 Scope 自动处理,无需额外处理 + return .none } } } @@ -87,4 +101,4 @@ struct HomeFeature { // MARK: - Notification Extension extension Notification.Name { static let homeLogout = Notification.Name("homeLogout") -} \ No newline at end of file +} diff --git a/yana/Info.plist b/yana/Info.plist index efb2927..d5b7ef5 100644 --- a/yana/Info.plist +++ b/yana/Info.plist @@ -2,10 +2,6 @@ - CFBundleDisplayName - E-PARTi - CFBundleName - E-PARTi NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/yana/Views/FeedView.swift b/yana/Views/FeedView.swift index 0cfa032..8483ff1 100644 --- a/yana/Views/FeedView.swift +++ b/yana/Views/FeedView.swift @@ -5,7 +5,7 @@ struct FeedView: View { let store: StoreOf var body: some View { - WithViewStore(store, observe: { $0 }) { viewStore in + WithPerceptionTracking { GeometryReader { geometry in ScrollView { VStack(spacing: 20) { @@ -44,44 +44,46 @@ struct FeedView: View { .padding(.horizontal, 30) .padding(.top, 20) - // 真实动态数据 - LazyVStack(spacing: 16) { - if viewStore.moments.isEmpty { - // 空状态 - VStack(spacing: 12) { - Image(systemName: "heart.text.square") - .font(.system(size: 40)) - .foregroundColor(.white.opacity(0.6)) - - Text("暂无动态内容") - .font(.system(size: 16)) - .foregroundColor(.white.opacity(0.8)) - - if let error = viewStore.error { - Text("错误: \(error)") - .font(.system(size: 12)) - .foregroundColor(.red.opacity(0.8)) - .multilineTextAlignment(.center) - .padding(.horizontal, 20) + // 真实动态数据 - 直接使用store状态 + WithPerceptionTracking { + LazyVStack(spacing: 16) { + if store.moments.isEmpty { + // 空状态 + VStack(spacing: 12) { + Image(systemName: "heart.text.square") + .font(.system(size: 40)) + .foregroundColor(.white.opacity(0.6)) + + Text("暂无动态内容") + .font(.system(size: 16)) + .foregroundColor(.white.opacity(0.8)) + + if let error = store.error { + Text("错误: \(error)") + .font(.system(size: 12)) + .foregroundColor(.red.opacity(0.8)) + .multilineTextAlignment(.center) + .padding(.horizontal, 20) + } + } + .padding(.top, 40) + } else { + // 显示真实动态数据 + ForEach(Array(store.moments.enumerated()), id: \.element.dynamicId) { index, moment in + OptimizedDynamicCardView( + moment: moment, + allMoments: store.moments, + currentIndex: index + ) } - } - .padding(.top, 40) - } else { - // 显示真实动态数据 - ForEach(Array(viewStore.moments.enumerated()), id: \.element.dynamicId) { index, moment in - OptimizedDynamicCardView( - moment: moment, - allMoments: viewStore.moments, - currentIndex: index - ) } } } .padding(.horizontal, 16) .padding(.top, 30) - // 加载状态 - if viewStore.isLoading { + // 加载状态 - 直接使用store状态 + if store.isLoading { HStack { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .white)) @@ -97,11 +99,11 @@ struct FeedView: View { } } .refreshable { - viewStore.send(.loadLatestMoments) + store.send(.loadLatestMoments) + } + .onAppear { + store.send(.onAppear) } - } - .onAppear { - viewStore.send(.onAppear) } } } @@ -292,9 +294,11 @@ struct OptimizedImageGrid: View { let imageSize: CGFloat = (availableWidth - spacing * 2) / 3 let columns = Array(repeating: GridItem(.fixed(imageSize), spacing: spacing), count: 3) - LazyVGrid(columns: columns, spacing: spacing) { - ForEach(images.prefix(9), id: \.id) { image in - SquareImageView(image: image, size: imageSize) + WithPerceptionTracking { + LazyVGrid(columns: columns, spacing: spacing) { + ForEach(images.prefix(9), id: \.id) { image in + SquareImageView(image: image, size: imageSize) + } } } } @@ -401,23 +405,25 @@ struct RealDynamicCardView: View { // 图片网格 if let images = moment.dynamicResList, !images.isEmpty { - LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: min(images.count, 3)), spacing: 8) { - ForEach(images.prefix(9), id: \.id) { image in - AsyncImage(url: URL(string: image.resUrl)) { imageView in - imageView - .resizable() - .aspectRatio(contentMode: .fill) - } placeholder: { - Rectangle() - .fill(Color.gray.opacity(0.3)) - .overlay( - ProgressView() - .progressViewStyle(CircularProgressViewStyle(tint: .white.opacity(0.6))) - ) + WithPerceptionTracking { + LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: min(images.count, 3)), spacing: 8) { + ForEach(images.prefix(9), id: \.id) { image in + AsyncImage(url: URL(string: image.resUrl)) { imageView in + imageView + .resizable() + .aspectRatio(contentMode: .fill) + } placeholder: { + Rectangle() + .fill(Color.gray.opacity(0.3)) + .overlay( + ProgressView() + .progressViewStyle(CircularProgressViewStyle(tint: .white.opacity(0.6))) + ) + } + .frame(height: 100) + .clipped() + .cornerRadius(8) } - .frame(height: 100) - .clipped() - .cornerRadius(8) } } } @@ -513,15 +519,17 @@ struct DynamicCardView: View { .multilineTextAlignment(.leading) // 图片网格 - LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: 3), spacing: 8) { - ForEach(0..<3) { imageIndex in - Rectangle() - .fill(Color.gray.opacity(0.3)) - .aspectRatio(1, contentMode: .fit) - .overlay( - Image(systemName: "photo") - .foregroundColor(.white.opacity(0.6)) - ) + WithPerceptionTracking { + LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: 3), spacing: 8) { + ForEach(0..<3) { imageIndex in + Rectangle() + .fill(Color.gray.opacity(0.3)) + .aspectRatio(1, contentMode: .fit) + .overlay( + Image(systemName: "photo") + .foregroundColor(.white.opacity(0.6)) + ) + } } } @@ -565,4 +573,4 @@ struct DynamicCardView: View { FeedFeature() } ) -} +} diff --git a/yana/Views/HomeView.swift b/yana/Views/HomeView.swift index c32a484..355a947 100644 --- a/yana/Views/HomeView.swift +++ b/yana/Views/HomeView.swift @@ -23,9 +23,7 @@ struct HomeView: View { switch selectedTab { case .feed: FeedView( - store: Store(initialState: FeedFeature.State()) { - FeedFeature() - } + store: store.scope(state: \.feedState, action: \.feed) ) .transition(.opacity) case .me: