83 lines
4.9 KiB
Markdown
83 lines
4.9 KiB
Markdown
|
|
# 完成页「强力侧滑版」— 审查报告(禁止应用)
|
|||
|
|
|
|||
|
|
**审查对象**:粉紫勋章视觉 + RobustSwipeBackEnabler(didMove + viewDidAppear 双节点强制开启侧滑)
|
|||
|
|
**结论**:仅审查,不修改仓库内任何文件。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、问题与方案对应
|
|||
|
|
|
|||
|
|
| 问题 | 方案 | 审查结论 |
|
|||
|
|
|------|------|----------|
|
|||
|
|
| 隐藏导航栏后侧滑返回失效 | 用 `UIViewController` 子类在 `didMove(toParent:)` 与 `viewDidAppear` 两处调用 `enableGesture()` | ✅ 思路正确,双节点可提高找回手势的概率 |
|
|||
|
|
| 之前 SwipeBackEnabler 时机不对或被覆盖 | 使用自定义 `SwipeBackController` 继承 `UIViewController`,在生命周期中多次执行 | ✅ 比仅用 `updateUIViewController` 的 Representable 更稳 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、强力侧滑实现审查
|
|||
|
|
|
|||
|
|
### 2.1 RobustSwipeBackEnabler + SwipeBackController
|
|||
|
|
|
|||
|
|
- **Representable**:`makeUIViewController` 返回 `SwipeBackController()`,`updateUIViewController` 为空,符合「只挂一个控制器做生命周期」的用法。
|
|||
|
|
- **SwipeBackController**:
|
|||
|
|
- `didMove(toParent:)`:视图被加入/移出父控制器时调用,此时通常已进入导航栈,可拿到 `navigationController`。
|
|||
|
|
- `viewDidAppear`:每次该页显示时再执行一次,可应对 SwiftUI 更新或系统把手势关掉的情况。
|
|||
|
|
- **enableGesture()**:
|
|||
|
|
`nc.interactivePopGestureRecognizer?.delegate = nil` + `isEnabled = true` 是常见做法,用于在隐藏导航栏时恢复全屏侧滑。逻辑正确。
|
|||
|
|
|
|||
|
|
### 2.2 潜在注意点(非否决项)
|
|||
|
|
|
|||
|
|
1. **视图层级**:`.background(RobustSwipeBackEnabler())` 会把 `SwipeBackController` 的 view 作为 CompletionView 的底层。SwiftUI 的 NavigationStack 对应底层会有一个 `UINavigationController`,`self.navigationController` 应是该栈,因此一般能正确找到。若将来把 CompletionView 放在非 NavigationStack 的容器里,需再确认 `navigationController` 是否仍为预期栈。
|
|||
|
|
2. **多次执行**:`didMove` / `viewDidAppear` 可能被多次调用(例如 SwiftUI 重绘、present 方式变化),重复执行 `enableGesture()` 无副作用,可接受。
|
|||
|
|
3. **真机验证**:建议在真机上从「发现 / 技能 / 我的」三个 Tab 分别进入完成页,各做几次右滑返回,确认无偶发失效;若仍有失效,可再在 `viewWillAppear` 或短延迟 `DispatchQueue.main.async` 中补一次 `enableGesture()`。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、视觉与交互审查
|
|||
|
|
|
|||
|
|
### 3.1 与需求对照
|
|||
|
|
|
|||
|
|
| 需求 | 实现 | 结论 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| 粉紫勋章:点前灰色+「完成」,点后粉紫+「共完成 XX 节」 | 未激活:灰底 + 灰边 + 「完成」;激活:粉紫渐变圆 + 白字「共完成 / N 小节」+ 冲击波 | ✅ 一致 |
|
|||
|
|
| 顶部无返回按钮,只有标题「已完成」 | `ZStack` 中仅 `Text("已完成")`,无 Button | ✅ 一致 |
|
|||
|
|
| 底部淡蓝按钮、无箭头 | `Text("回到我的内容")`,`.foregroundColor(.brandVital)`,`Capsule().fill(Color.brandVital.opacity(0.08))`,无 SF Symbol | ✅ 一致 |
|
|||
|
|
| 右滑回最后一节 | 依赖 RobustSwipeBackEnabler | ✅ 见上 |
|
|||
|
|
| 底部按钮回技能页根 | `handleReturnToRoot()` → `navStore.switchToGrowthTab()` | ✅ 一致 |
|
|||
|
|
|
|||
|
|
### 3.2 配色与 DesignSystem
|
|||
|
|
|
|||
|
|
- 方案中本地定义:
|
|||
|
|
- `cyberPink = Color(red: 1.0, green: 0.25, blue: 0.50)` → 等价 `#FF4081`
|
|||
|
|
- `cyberPurple = Color(red: 0.54, green: 0.31, blue: 1.0)` → 等价 `#8A4FFF`
|
|||
|
|
- **DesignSystem** 中已有:
|
|||
|
|
- `Color.cyberNeon` = `#FF4081`
|
|||
|
|
- `Color.cyberIris` = `#8A4FFF`
|
|||
|
|
- **建议**(可选):将勋章渐变的 `cyberPink` / `cyberPurple` 改为 `Color.cyberNeon` / `Color.cyberIris`,避免重复定义并统一设计系统;若你希望完成页与 DesignSystem 解耦则可保留当前写法。
|
|||
|
|
|
|||
|
|
### 3.3 其他细节
|
|||
|
|
|
|||
|
|
- 冲击波:`rippleScale` 从 1.0 到 1.8、`rippleOpacity` 从 1.0 到 0,时长 0.8s,视觉合理。
|
|||
|
|
- 震动:`UIImpactFeedbackGenerator(style: .heavy)`,与「点亮」动作匹配。
|
|||
|
|
- 底部按钮常驻显示(不依赖 `isActive`),与「回到我的内容」随时可点一致。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、接口与调用方
|
|||
|
|
|
|||
|
|
- **CompletionView** 仍为三参:`courseId`, `courseTitle`, `completedLessonCount`,依赖 `@EnvironmentObject navStore`。
|
|||
|
|
- **GrowthView / ProfileView / DiscoveryView** 无需改构造或传参。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 五、审查结论汇总
|
|||
|
|
|
|||
|
|
| 项 | 结论 |
|
|||
|
|
|----|------|
|
|||
|
|
| 强力侧滑方案 | 双生命周期节点(didMove + viewDidAppear)合理,实现正确;建议真机多入口验证,必要时可再加 viewWillAppear 或短延迟兜底。 |
|
|||
|
|
| 粉紫勋章视觉 | 与描述一致;可选改用 DesignSystem 的 cyberNeon/cyberIris 统一色值。 |
|
|||
|
|
| 顶部 / 底部 / 交互 | 符合「无返回、仅标题、淡蓝按钮无箭头、底部回技能页根」的要求。 |
|
|||
|
|
| 全量替换范围 | 仅替换 `CompletionView.swift` 即可;VerticalScreenPlayerView 等不动。 |
|
|||
|
|
|
|||
|
|
**未对仓库内任何文件进行修改。**
|