feat: 更新Swift助手样式和动态视图组件

- 在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版本,确保兼容性。
This commit is contained in:
edwinQQQ
2025-07-14 14:50:15 +08:00
parent f686480cdc
commit 1f98ed534d
11 changed files with 232 additions and 159 deletions

View File

@@ -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.

View File

@@ -1,7 +1,5 @@
---
description:
globs:
alwaysApply: true
alwaysApply: false
---
You are an expert iOS developer using Swift and SwiftUI. Follow these guidelines:

View File

@@ -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";
};

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1640"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4C3E651E2DB61F7A00E5A455"
BuildableName = "yana.app"
BlueprintName = "yana"
ReferencedContainer = "container:yana.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4C4C8FBC2DE5AF9200384527"
BuildableName = "yanaAPITests.xctest"
BlueprintName = "yanaAPITests"
ReferencedContainer = "container:yana.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4C3E651E2DB61F7A00E5A455"
BuildableName = "yana.app"
BlueprintName = "yana"
ReferencedContainer = "container:yana.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4C3E651E2DB61F7A00E5A455"
BuildableName = "yana.app"
BlueprintName = "yana"
ReferencedContainer = "container:yana.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -10,5 +10,18 @@
<integer>3</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>4C3E651E2DB61F7A00E5A455</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>4C4C8FBC2DE5AF9200384527</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict>
</dict>
</plist>

View File

@@ -3,38 +3,4 @@
uuid = "A60FAB2A-3184-45B2-920F-A3D7A086CF95"
type = "0"
version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "4D63F38A-4F7C-46D9-8CAF-BCA831664FA0"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "yana/APIs/APIService.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "126"
endingLineNumber = "126"
landmarkName = "request(_:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "19930D63-5B42-4287-8B22-ADF87CAD40E3"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "yana/APIs/APIService.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "112"
endingLineNumber = "112"
landmarkName = "request(_:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>

View File

@@ -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:

View File

@@ -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<Self> {
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")
}
}

View File

@@ -2,10 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>E-PARTi</string>
<key>CFBundleName</key>
<string>E-PARTi</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>

View File

@@ -5,7 +5,7 @@ struct FeedView: View {
let store: StoreOf<FeedFeature>
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()
}
)
}
}

View File

@@ -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: