feat: 添加设置功能和动态视图
- 新增设置功能模块,包含用户信息管理和设置选项。 - 实现动态视图,展示用户动态内容。 - 更新HomeView以支持设置页面的展示和动态视图的切换。 - 添加底部导航栏,增强用户体验。 - 更新相关视图和组件,确保一致的UI风格和交互体验。
This commit is contained in:
@@ -47,6 +47,8 @@
|
|||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
4C4C8FBE2DE5AF9200384527 /* yanaAPITests */ = {
|
4C4C8FBE2DE5AF9200384527 /* yanaAPITests */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
);
|
||||||
path = yanaAPITests;
|
path = yanaAPITests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -269,14 +271,10 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-yana/Pods-yana-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-yana/Pods-yana-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-yana/Pods-yana-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-yana/Pods-yana-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-yana/Pods-yana-frameworks.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-yana/Pods-yana-frameworks.sh\"\n";
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
|
"filename" : "logo.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"platform" : "ios",
|
"platform" : "ios",
|
||||||
"size" : "1024x1024"
|
"size" : "1024x1024"
|
||||||
|
BIN
yana/Assets.xcassets/AppIcon.appiconset/logo.png
Normal file
BIN
yana/Assets.xcassets/AppIcon.appiconset/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 802 KiB |
6
yana/Assets.xcassets/Home/Contents.json
Normal file
6
yana/Assets.xcassets/Home/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
21
yana/Assets.xcassets/Home/add icon.imageset/Contents.json
vendored
Normal file
21
yana/Assets.xcassets/Home/add icon.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "发布@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
yana/Assets.xcassets/Home/add icon.imageset/发布@3x.png
vendored
Normal file
BIN
yana/Assets.xcassets/Home/add icon.imageset/发布@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
yana/Assets.xcassets/Home/feed selected.imageset/3@3x.png
vendored
Normal file
BIN
yana/Assets.xcassets/Home/feed selected.imageset/3@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
21
yana/Assets.xcassets/Home/feed selected.imageset/Contents.json
vendored
Normal file
21
yana/Assets.xcassets/Home/feed selected.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "3@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
yana/Assets.xcassets/Home/feed unselected.imageset/3@3x (1).png
vendored
Normal file
BIN
yana/Assets.xcassets/Home/feed unselected.imageset/3@3x (1).png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
21
yana/Assets.xcassets/Home/feed unselected.imageset/Contents.json
vendored
Normal file
21
yana/Assets.xcassets/Home/feed unselected.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "3@3x (1).png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
yana/Assets.xcassets/Home/me selected.imageset/5@3x (1).png
vendored
Normal file
BIN
yana/Assets.xcassets/Home/me selected.imageset/5@3x (1).png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.5 KiB |
21
yana/Assets.xcassets/Home/me selected.imageset/Contents.json
vendored
Normal file
21
yana/Assets.xcassets/Home/me selected.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "5@3x (1).png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
yana/Assets.xcassets/Home/me unselected.imageset/5@3x.png
vendored
Normal file
BIN
yana/Assets.xcassets/Home/me unselected.imageset/5@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
21
yana/Assets.xcassets/Home/me unselected.imageset/Contents.json
vendored
Normal file
21
yana/Assets.xcassets/Home/me unselected.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "5@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
@@ -9,6 +9,10 @@ struct HomeFeature {
|
|||||||
var userInfo: UserInfo?
|
var userInfo: UserInfo?
|
||||||
var accountModel: AccountModel?
|
var accountModel: AccountModel?
|
||||||
var error: String?
|
var error: String?
|
||||||
|
|
||||||
|
// 设置页面相关状态
|
||||||
|
var isSettingPresented = false
|
||||||
|
var settingState = SettingFeature.State()
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Action: Equatable {
|
enum Action: Equatable {
|
||||||
@@ -19,9 +23,17 @@ struct HomeFeature {
|
|||||||
case accountModelLoaded(AccountModel?)
|
case accountModelLoaded(AccountModel?)
|
||||||
case logoutTapped
|
case logoutTapped
|
||||||
case logout
|
case logout
|
||||||
|
|
||||||
|
// 设置页面相关actions
|
||||||
|
case settingDismissed
|
||||||
|
case setting(SettingFeature.Action)
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some ReducerOf<Self> {
|
var body: some ReducerOf<Self> {
|
||||||
|
Scope(state: \.settingState, action: \.setting) {
|
||||||
|
SettingFeature()
|
||||||
|
}
|
||||||
|
|
||||||
Reduce { state, action in
|
Reduce { state, action in
|
||||||
switch action {
|
switch action {
|
||||||
case .onAppear:
|
case .onAppear:
|
||||||
@@ -59,6 +71,14 @@ struct HomeFeature {
|
|||||||
// 发送通知返回登录页面
|
// 发送通知返回登录页面
|
||||||
NotificationCenter.default.post(name: .homeLogout, object: nil)
|
NotificationCenter.default.post(name: .homeLogout, object: nil)
|
||||||
return .none
|
return .none
|
||||||
|
|
||||||
|
case .settingDismissed:
|
||||||
|
state.isSettingPresented = false
|
||||||
|
return .none
|
||||||
|
|
||||||
|
case .setting:
|
||||||
|
// 由子reducer处理
|
||||||
|
return .none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
75
yana/Features/SettingFeature.swift
Normal file
75
yana/Features/SettingFeature.swift
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import Foundation
|
||||||
|
import ComposableArchitecture
|
||||||
|
|
||||||
|
@Reducer
|
||||||
|
struct SettingFeature {
|
||||||
|
@ObservableState
|
||||||
|
struct State: Equatable {
|
||||||
|
var userInfo: UserInfo?
|
||||||
|
var accountModel: AccountModel?
|
||||||
|
var isLoading = false
|
||||||
|
var error: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Action: Equatable {
|
||||||
|
case onAppear
|
||||||
|
case loadUserInfo
|
||||||
|
case userInfoLoaded(UserInfo?)
|
||||||
|
case loadAccountModel
|
||||||
|
case accountModelLoaded(AccountModel?)
|
||||||
|
case logoutTapped
|
||||||
|
case logout
|
||||||
|
case dismissTapped
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some ReducerOf<Self> {
|
||||||
|
Reduce { state, action in
|
||||||
|
switch action {
|
||||||
|
case .onAppear:
|
||||||
|
return .concatenate(
|
||||||
|
.send(.loadUserInfo),
|
||||||
|
.send(.loadAccountModel)
|
||||||
|
)
|
||||||
|
|
||||||
|
case .loadUserInfo:
|
||||||
|
let userInfo = UserInfoManager.getUserInfo()
|
||||||
|
return .send(.userInfoLoaded(userInfo))
|
||||||
|
|
||||||
|
case let .userInfoLoaded(userInfo):
|
||||||
|
state.userInfo = userInfo
|
||||||
|
return .none
|
||||||
|
|
||||||
|
case .loadAccountModel:
|
||||||
|
let accountModel = UserInfoManager.getAccountModel()
|
||||||
|
return .send(.accountModelLoaded(accountModel))
|
||||||
|
|
||||||
|
case let .accountModelLoaded(accountModel):
|
||||||
|
state.accountModel = accountModel
|
||||||
|
return .none
|
||||||
|
|
||||||
|
case .logoutTapped:
|
||||||
|
return .send(.logout)
|
||||||
|
|
||||||
|
case .logout:
|
||||||
|
state.isLoading = true
|
||||||
|
|
||||||
|
// 清除所有认证数据
|
||||||
|
UserInfoManager.clearAllAuthenticationData()
|
||||||
|
|
||||||
|
// 发送通知返回登录页面
|
||||||
|
NotificationCenter.default.post(name: .homeLogout, object: nil)
|
||||||
|
return .none
|
||||||
|
|
||||||
|
case .dismissTapped:
|
||||||
|
// 发送关闭设置页面的通知
|
||||||
|
NotificationCenter.default.post(name: .settingsDismiss, object: nil)
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Notification Extension
|
||||||
|
extension Notification.Name {
|
||||||
|
static let settingsDismiss = Notification.Name("settingsDismiss")
|
||||||
|
}
|
@@ -2,6 +2,10 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>E-PARTi</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>E-PARTi</string>
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
|
73
yana/Views/Components/BottomTabView.swift
Normal file
73
yana/Views/Components/BottomTabView.swift
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
// MARK: - Tab 枚举
|
||||||
|
enum Tab: Int, CaseIterable {
|
||||||
|
case feed = 0
|
||||||
|
case me = 1
|
||||||
|
|
||||||
|
var title: String {
|
||||||
|
switch self {
|
||||||
|
case .feed:
|
||||||
|
return "动态"
|
||||||
|
case .me:
|
||||||
|
return "我的"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var iconName: String {
|
||||||
|
switch self {
|
||||||
|
case .feed:
|
||||||
|
return "sparkles"
|
||||||
|
case .me:
|
||||||
|
return "person"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedIconName: String {
|
||||||
|
switch self {
|
||||||
|
case .feed:
|
||||||
|
return "sparkles"
|
||||||
|
case .me:
|
||||||
|
return "person.fill"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - BottomTabView 组件
|
||||||
|
struct BottomTabView: View {
|
||||||
|
@Binding var selectedTab: Tab
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
ForEach(Tab.allCases, id: \.rawValue) { tab in
|
||||||
|
Button(action: {
|
||||||
|
selectedTab = tab
|
||||||
|
}) {
|
||||||
|
VStack(spacing: 4) {
|
||||||
|
Image(systemName: selectedTab == tab ? tab.selectedIconName : tab.iconName)
|
||||||
|
.font(.system(size: 20))
|
||||||
|
.foregroundColor(selectedTab == tab ? .purple : .gray)
|
||||||
|
|
||||||
|
Text(tab.title)
|
||||||
|
.font(.caption2)
|
||||||
|
.foregroundColor(selectedTab == tab ? .purple : .gray)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.vertical, 12)
|
||||||
|
.padding(.horizontal, 8)
|
||||||
|
.background(
|
||||||
|
.ultraThinMaterial,
|
||||||
|
in: Rectangle()
|
||||||
|
)
|
||||||
|
.overlay(
|
||||||
|
Rectangle()
|
||||||
|
.frame(height: 0.5)
|
||||||
|
.foregroundColor(.black.opacity(0.2)),
|
||||||
|
alignment: .top
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
134
yana/Views/FeedView.swift
Normal file
134
yana/Views/FeedView.swift
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct FeedView: View {
|
||||||
|
var body: some View {
|
||||||
|
ScrollView {
|
||||||
|
VStack(spacing: 20) {
|
||||||
|
// 顶部标题
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Text("Enjoy your Life Time")
|
||||||
|
.font(.system(size: 22, weight: .semibold))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.top, 20)
|
||||||
|
|
||||||
|
// 心脏图标
|
||||||
|
Image(systemName: "heart.fill")
|
||||||
|
.font(.system(size: 60))
|
||||||
|
.foregroundColor(.red)
|
||||||
|
.padding(.top, 40)
|
||||||
|
|
||||||
|
// 励志文字
|
||||||
|
Text("The disease is like a cruel ruler,\nand time is our most precious treasure.\nEvery moment we live is a victory\nagainst the inevitable.")
|
||||||
|
.font(.system(size: 16))
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.foregroundColor(.white.opacity(0.9))
|
||||||
|
.padding(.horizontal, 30)
|
||||||
|
.padding(.top, 20)
|
||||||
|
|
||||||
|
// 模拟动态卡片
|
||||||
|
LazyVStack(spacing: 16) {
|
||||||
|
ForEach(0..<3) { index in
|
||||||
|
DynamicCardView(index: index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 16)
|
||||||
|
.padding(.top, 30)
|
||||||
|
|
||||||
|
// 底部安全区域
|
||||||
|
Color.clear.frame(height: 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 动态卡片组件
|
||||||
|
struct DynamicCardView: View {
|
||||||
|
let index: Int
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
|
// 用户信息行
|
||||||
|
HStack(spacing: 12) {
|
||||||
|
// 头像
|
||||||
|
Circle()
|
||||||
|
.fill(Color.blue.opacity(0.6))
|
||||||
|
.frame(width: 40, height: 40)
|
||||||
|
.overlay(
|
||||||
|
Text("👤")
|
||||||
|
.font(.system(size: 20))
|
||||||
|
)
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
HStack {
|
||||||
|
Text("NAMENAMENAME....")
|
||||||
|
.font(.system(size: 14, weight: .medium))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Text("ID:7271557")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.white.opacity(0.7))
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("09/12")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.white.opacity(0.7))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内容文字
|
||||||
|
Text("这是动态内容 \(index + 1)。今天是美好的一天,分享一些生活中的点点滴滴。")
|
||||||
|
.font(.system(size: 15))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.lineLimit(nil)
|
||||||
|
|
||||||
|
// 图片网格(模拟)
|
||||||
|
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))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 互动按钮
|
||||||
|
HStack(spacing: 20) {
|
||||||
|
Button(action: {}) {
|
||||||
|
HStack(spacing: 4) {
|
||||||
|
Image(systemName: "message")
|
||||||
|
.font(.system(size: 16))
|
||||||
|
Text("354")
|
||||||
|
.font(.system(size: 14))
|
||||||
|
}
|
||||||
|
.foregroundColor(.white.opacity(0.8))
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(action: {}) {
|
||||||
|
HStack(spacing: 4) {
|
||||||
|
Image(systemName: "heart")
|
||||||
|
.font(.system(size: 16))
|
||||||
|
Text("354")
|
||||||
|
.font(.system(size: 14))
|
||||||
|
}
|
||||||
|
.foregroundColor(.white.opacity(0.8))
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.top, 8)
|
||||||
|
}
|
||||||
|
.padding(16)
|
||||||
|
.background(
|
||||||
|
Color.white.opacity(0.1)
|
||||||
|
.cornerRadius(12)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@@ -4,111 +4,70 @@ import ComposableArchitecture
|
|||||||
struct HomeView: View {
|
struct HomeView: View {
|
||||||
let store: StoreOf<HomeFeature>
|
let store: StoreOf<HomeFeature>
|
||||||
@ObservedObject private var localizationManager = LocalizationManager.shared
|
@ObservedObject private var localizationManager = LocalizationManager.shared
|
||||||
|
@State private var selectedTab: Tab = .feed
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
WithPerceptionTracking {
|
WithPerceptionTracking {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
ZStack {
|
ZStack {
|
||||||
// 背景图片 - 使用"bg"图片,全屏显示
|
// 使用 "bg" 图片作为背景
|
||||||
Image("bg")
|
Image("bg")
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.frame(width: geometry.size.width, height: geometry.size.height)
|
||||||
|
.clipped()
|
||||||
.ignoresSafeArea(.all)
|
.ignoresSafeArea(.all)
|
||||||
|
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
// Navigation Bar 标题区域
|
// 顶部导航区域
|
||||||
Text("home.title".localized)
|
HStack {
|
||||||
.font(.custom("PingFang SC-Semibold", size: 16))
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.frame(
|
|
||||||
width: 158,
|
|
||||||
height: 22,
|
|
||||||
alignment: .center
|
|
||||||
) // 参考代码中的尺寸
|
|
||||||
.padding(.top, 8)
|
|
||||||
.padding(.horizontal)
|
|
||||||
|
|
||||||
// 中间内容区域
|
|
||||||
VStack(spacing: 32) {
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
// 用户信息区域
|
// 右上角加号按钮
|
||||||
VStack(spacing: 16) {
|
|
||||||
// 优先显示 UserInfo 中的用户名,否则显示通用欢迎信息
|
|
||||||
if let userInfo = store.userInfo, let userName = userInfo.username {
|
|
||||||
Text("欢迎, \(userName)")
|
|
||||||
.font(.title2)
|
|
||||||
.foregroundColor(.white)
|
|
||||||
} else {
|
|
||||||
Text("欢迎")
|
|
||||||
.font(.title2)
|
|
||||||
.foregroundColor(.white)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示用户ID信息:优先 UserInfo,其次 AccountModel
|
|
||||||
if let userInfo = store.userInfo, let userId = userInfo.userId {
|
|
||||||
Text("ID: \(userId)")
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundColor(.white.opacity(0.8))
|
|
||||||
} else if let accountModel = store.accountModel, let uid = accountModel.uid {
|
|
||||||
Text("UID: \(uid)")
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundColor(.white.opacity(0.8))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示账户状态(如果有 AccountModel)
|
|
||||||
if let accountModel = store.accountModel {
|
|
||||||
VStack(spacing: 4) {
|
|
||||||
if accountModel.hasValidSession {
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
|
||||||
.foregroundColor(.green)
|
|
||||||
Text("已登录")
|
|
||||||
.foregroundColor(.white.opacity(0.9))
|
|
||||||
}
|
|
||||||
.font(.caption)
|
|
||||||
} else if accountModel.hasValidAuthentication {
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "clock.circle.fill")
|
|
||||||
.foregroundColor(.orange)
|
|
||||||
Text("认证中")
|
|
||||||
.foregroundColor(.white.opacity(0.9))
|
|
||||||
}
|
|
||||||
.font(.caption)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
.background(Color.black.opacity(0.3))
|
|
||||||
.cornerRadius(12)
|
|
||||||
.padding(.horizontal, 32)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
// 登出按钮
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
store.send(.logoutTapped)
|
// 加号按钮操作
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
Image(systemName: "plus")
|
||||||
Image(systemName: "arrow.right.square")
|
.font(.system(size: 20, weight: .medium))
|
||||||
Text("退出登录")
|
.foregroundColor(.red)
|
||||||
|
.frame(width: 36, height: 36)
|
||||||
|
.background(
|
||||||
|
Color.white.opacity(0.2)
|
||||||
|
.cornerRadius(18)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.font(.body)
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.padding(.horizontal, 24)
|
|
||||||
.padding(.vertical, 12)
|
|
||||||
.background(Color.red.opacity(0.7))
|
|
||||||
.cornerRadius(8)
|
|
||||||
}
|
}
|
||||||
.padding(.bottom, 50)
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.top, 10)
|
||||||
|
|
||||||
|
// 主要内容区域
|
||||||
|
ZStack {
|
||||||
|
// 根据选中的 tab 显示不同的视图
|
||||||
|
switch selectedTab {
|
||||||
|
case .feed:
|
||||||
|
FeedView()
|
||||||
|
.transition(.opacity)
|
||||||
|
case .me:
|
||||||
|
MeView()
|
||||||
|
.transition(.opacity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
|
||||||
|
// 底部导航栏
|
||||||
|
BottomTabView(selectedTab: $selectedTab)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
store.send(.onAppear)
|
store.send(.onAppear)
|
||||||
}
|
}
|
||||||
|
.sheet(isPresented: Binding(
|
||||||
|
get: { store.isSettingPresented },
|
||||||
|
set: { _ in store.send(.settingDismissed) }
|
||||||
|
)) {
|
||||||
|
SettingView(store: store.scope(state: \.settingState, action: \.setting))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
107
yana/Views/MeView.swift
Normal file
107
yana/Views/MeView.swift
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct MeView: View {
|
||||||
|
var body: some View {
|
||||||
|
ScrollView {
|
||||||
|
VStack(spacing: 20) {
|
||||||
|
// 顶部标题
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Text("我的")
|
||||||
|
.font(.system(size: 22, weight: .semibold))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.top, 20)
|
||||||
|
|
||||||
|
// 用户头像区域
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
Circle()
|
||||||
|
.fill(Color.white.opacity(0.2))
|
||||||
|
.frame(width: 80, height: 80)
|
||||||
|
.overlay(
|
||||||
|
Image(systemName: "person.fill")
|
||||||
|
.font(.system(size: 40))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text("用户昵称")
|
||||||
|
.font(.system(size: 18, weight: .medium))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
Text("ID: 123456789")
|
||||||
|
.font(.system(size: 14))
|
||||||
|
.foregroundColor(.white.opacity(0.7))
|
||||||
|
}
|
||||||
|
.padding(.top, 30)
|
||||||
|
|
||||||
|
// 功能菜单
|
||||||
|
VStack(spacing: 12) {
|
||||||
|
MenuItemView(icon: "gearshape", title: "设置", action: {})
|
||||||
|
MenuItemView(icon: "person.circle", title: "个人信息", action: {})
|
||||||
|
MenuItemView(icon: "heart", title: "我的收藏", action: {})
|
||||||
|
MenuItemView(icon: "clock", title: "浏览历史", action: {})
|
||||||
|
MenuItemView(icon: "questionmark.circle", title: "帮助与反馈", action: {})
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.top, 40)
|
||||||
|
|
||||||
|
// 退出登录按钮
|
||||||
|
Button(action: {}) {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "rectangle.portrait.and.arrow.right")
|
||||||
|
.font(.system(size: 16))
|
||||||
|
Text("退出登录")
|
||||||
|
.font(.system(size: 16, weight: .medium))
|
||||||
|
}
|
||||||
|
.foregroundColor(.red)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.frame(height: 50)
|
||||||
|
.background(
|
||||||
|
Color.white.opacity(0.1)
|
||||||
|
.cornerRadius(12)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.top, 30)
|
||||||
|
|
||||||
|
// 底部安全区域
|
||||||
|
Color.clear.frame(height: 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 菜单项组件
|
||||||
|
struct MenuItemView: View {
|
||||||
|
let icon: String
|
||||||
|
let title: String
|
||||||
|
let action: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button(action: action) {
|
||||||
|
HStack(spacing: 16) {
|
||||||
|
Image(systemName: icon)
|
||||||
|
.font(.system(size: 20))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.frame(width: 24)
|
||||||
|
|
||||||
|
Text(title)
|
||||||
|
.font(.system(size: 16))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
||||||
|
Image(systemName: "chevron.right")
|
||||||
|
.font(.system(size: 14))
|
||||||
|
.foregroundColor(.white.opacity(0.6))
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.frame(height: 56)
|
||||||
|
.background(
|
||||||
|
Color.white.opacity(0.1)
|
||||||
|
.cornerRadius(12)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
}
|
||||||
|
}
|
199
yana/Views/SettingView.swift
Normal file
199
yana/Views/SettingView.swift
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import ComposableArchitecture
|
||||||
|
|
||||||
|
struct SettingView: View {
|
||||||
|
let store: StoreOf<SettingFeature>
|
||||||
|
@ObservedObject private var localizationManager = LocalizationManager.shared
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
WithPerceptionTracking {
|
||||||
|
GeometryReader { geometry in
|
||||||
|
ZStack {
|
||||||
|
// 背景图片 - 使用"bg"图片,全屏显示
|
||||||
|
Image("bg")
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.ignoresSafeArea(.all)
|
||||||
|
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
// Navigation Bar
|
||||||
|
HStack {
|
||||||
|
// 返回按钮
|
||||||
|
Button(action: {
|
||||||
|
store.send(.dismissTapped)
|
||||||
|
}) {
|
||||||
|
Image(systemName: "chevron.left")
|
||||||
|
.font(.title2)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
}
|
||||||
|
.padding(.leading, 16)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
Text("设置")
|
||||||
|
.font(.custom("PingFang SC-Semibold", size: 16))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
// 占位符,保持标题居中
|
||||||
|
Color.clear
|
||||||
|
.frame(width: 44, height: 44)
|
||||||
|
}
|
||||||
|
.padding(.top, 8)
|
||||||
|
.padding(.horizontal)
|
||||||
|
|
||||||
|
// 内容区域
|
||||||
|
ScrollView {
|
||||||
|
VStack(spacing: 24) {
|
||||||
|
// 用户信息卡片
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
// 头像区域
|
||||||
|
Image(systemName: "person.circle.fill")
|
||||||
|
.font(.system(size: 60))
|
||||||
|
.foregroundColor(.white.opacity(0.8))
|
||||||
|
|
||||||
|
// 用户信息
|
||||||
|
VStack(spacing: 8) {
|
||||||
|
if let userInfo = store.userInfo, let userName = userInfo.username {
|
||||||
|
Text(userName)
|
||||||
|
.font(.title2.weight(.semibold))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
} else {
|
||||||
|
Text("用户")
|
||||||
|
.font(.title2.weight(.semibold))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示用户ID
|
||||||
|
if let userInfo = store.userInfo, let userId = userInfo.userId {
|
||||||
|
Text("ID: \(userId)")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.white.opacity(0.8))
|
||||||
|
} else if let accountModel = store.accountModel, let uid = accountModel.uid {
|
||||||
|
Text("UID: \(uid)")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.white.opacity(0.8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.vertical, 24)
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.background(Color.black.opacity(0.3))
|
||||||
|
.cornerRadius(16)
|
||||||
|
.padding(.horizontal, 24)
|
||||||
|
.padding(.top, 32)
|
||||||
|
|
||||||
|
// 设置选项列表
|
||||||
|
VStack(spacing: 12) {
|
||||||
|
// 语言设置
|
||||||
|
SettingRowView(
|
||||||
|
icon: "globe",
|
||||||
|
title: "语言设置",
|
||||||
|
action: {
|
||||||
|
// TODO: 实现语言设置
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 关于我们
|
||||||
|
SettingRowView(
|
||||||
|
icon: "info.circle",
|
||||||
|
title: "关于我们",
|
||||||
|
action: {
|
||||||
|
// TODO: 实现关于页面
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 版本信息
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "app.badge")
|
||||||
|
.foregroundColor(.white.opacity(0.8))
|
||||||
|
.frame(width: 24)
|
||||||
|
|
||||||
|
Text("版本信息")
|
||||||
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Text("1.0.0")
|
||||||
|
.foregroundColor(.white.opacity(0.6))
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.vertical, 16)
|
||||||
|
.background(Color.black.opacity(0.2))
|
||||||
|
.cornerRadius(12)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 24)
|
||||||
|
|
||||||
|
Spacer(minLength: 50)
|
||||||
|
|
||||||
|
// 退出登录按钮
|
||||||
|
Button(action: {
|
||||||
|
store.send(.logoutTapped)
|
||||||
|
}) {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "arrow.right.square")
|
||||||
|
Text("退出登录")
|
||||||
|
}
|
||||||
|
.font(.body.weight(.medium))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding(.vertical, 16)
|
||||||
|
.background(Color.red.opacity(0.7))
|
||||||
|
.cornerRadius(12)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 24)
|
||||||
|
.padding(.bottom, 50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
store.send(.onAppear)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Setting Row View
|
||||||
|
struct SettingRowView: View {
|
||||||
|
let icon: String
|
||||||
|
let title: String
|
||||||
|
let action: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button(action: action) {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: icon)
|
||||||
|
.foregroundColor(.white.opacity(0.8))
|
||||||
|
.frame(width: 24)
|
||||||
|
|
||||||
|
Text(title)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Image(systemName: "chevron.right")
|
||||||
|
.foregroundColor(.white.opacity(0.6))
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.vertical, 16)
|
||||||
|
.background(Color.black.opacity(0.2))
|
||||||
|
.cornerRadius(12)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
SettingView(
|
||||||
|
store: Store(
|
||||||
|
initialState: SettingFeature.State()
|
||||||
|
) {
|
||||||
|
SettingFeature()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
269
项目问题排查与解决流程.md
Normal file
269
项目问题排查与解决流程.md
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
# 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<Self> {
|
||||||
|
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 15.6+, Swift 6, TCA 1.20.2+
|
||||||
|
**维护者**: AI Assistant & 开发团队
|
Reference in New Issue
Block a user