|
|
# 授权界面修复报告
|
|
|
|
|
|
## 问题描述
|
|
|
|
|
|
用户反馈授权界面的相机和相册授权按钮不起作用,无法直接在应用内授权,需要跳转到系统界面。
|
|
|
|
|
|
## 问题分析
|
|
|
|
|
|
### 原始问题
|
|
|
1. **权限请求回调处理不当**: 在 SwiftUI View 中错误使用了 `[weak self]` 捕获列表
|
|
|
2. **缺少加载状态反馈**: 用户点击按钮后没有视觉反馈,不知道请求是否在进行
|
|
|
3. **权限状态更新不及时**: 权限请求完成后状态更新可能延迟
|
|
|
4. **缺少触觉反馈**: 用户操作后没有触觉反馈确认
|
|
|
5. **按钮状态管理不完善**: 已授权状态下按钮仍然可点击
|
|
|
|
|
|
## 修复方案
|
|
|
|
|
|
### 1. 修复权限请求回调
|
|
|
**问题**: 在 SwiftUI View 结构体中使用 `[weak self]` 导致编译错误
|
|
|
```swift
|
|
|
// 错误代码
|
|
|
AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
|
|
|
// ...
|
|
|
}
|
|
|
|
|
|
// 修复后
|
|
|
AVCaptureDevice.requestAccess(for: .video) { granted in
|
|
|
// ...
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 2. 添加加载状态管理
|
|
|
**新增状态变量**:
|
|
|
```swift
|
|
|
@State private var isRequestingCameraPermission = false
|
|
|
@State private var isRequestingPhotoPermission = false
|
|
|
```
|
|
|
|
|
|
**权限请求流程**:
|
|
|
```swift
|
|
|
private func requestCameraPermission() {
|
|
|
logInfo("🔐 请求相机权限", className: "AppPermissionsView")
|
|
|
|
|
|
// 设置加载状态
|
|
|
isRequestingCameraPermission = true
|
|
|
|
|
|
AVCaptureDevice.requestAccess(for: .video) { granted in
|
|
|
DispatchQueue.main.async {
|
|
|
// 清除加载状态
|
|
|
self.isRequestingCameraPermission = false
|
|
|
|
|
|
if granted {
|
|
|
logInfo("✅ 相机权限请求成功", className: "AppPermissionsView")
|
|
|
self.cameraPermissionStatus = .authorized
|
|
|
} else {
|
|
|
logWarning("❌ 相机权限请求被拒绝", className: "AppPermissionsView")
|
|
|
self.cameraPermissionStatus = .denied
|
|
|
}
|
|
|
|
|
|
// 添加触觉反馈
|
|
|
let impactFeedback = UIImpactFeedbackGenerator(style: .medium)
|
|
|
impactFeedback.impactOccurred()
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 3. 改进权限卡片组件
|
|
|
**新增功能**:
|
|
|
- 加载状态指示器 (ProgressView)
|
|
|
- 按钮按下动画效果
|
|
|
- 图标和文字组合显示
|
|
|
- 权限状态相关的按钮禁用逻辑
|
|
|
|
|
|
```swift
|
|
|
struct PermissionCard: View {
|
|
|
let icon: String
|
|
|
let iconColor: Color
|
|
|
let title: String
|
|
|
let description: String
|
|
|
let status: String
|
|
|
let statusColor: Color
|
|
|
let action: () -> Void
|
|
|
let actionTitle: String
|
|
|
let isLoading: Bool // 新增加载状态参数
|
|
|
|
|
|
@State private var isButtonPressed = false
|
|
|
|
|
|
var body: some View {
|
|
|
// ... UI 实现
|
|
|
Button(action: {
|
|
|
// 添加按钮按下动画
|
|
|
withAnimation(.easeInOut(duration: 0.1)) {
|
|
|
isButtonPressed = true
|
|
|
}
|
|
|
|
|
|
// 执行权限请求
|
|
|
action()
|
|
|
|
|
|
// 重置按钮状态
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
|
withAnimation(.easeInOut(duration: 0.1)) {
|
|
|
isButtonPressed = false
|
|
|
}
|
|
|
}
|
|
|
}) {
|
|
|
HStack(spacing: 6) {
|
|
|
if isLoading {
|
|
|
ProgressView()
|
|
|
.scaleEffect(0.8)
|
|
|
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
|
|
} else if actionTitle == "request_permission".localized {
|
|
|
Image(systemName: "hand.raised.fill")
|
|
|
.font(.system(size: 12, weight: .medium))
|
|
|
} else if actionTitle == "open_settings".localized {
|
|
|
Image(systemName: "gear")
|
|
|
.font(.system(size: 12, weight: .medium))
|
|
|
} else {
|
|
|
Image(systemName: "checkmark.circle.fill")
|
|
|
.font(.system(size: 12, weight: .medium))
|
|
|
}
|
|
|
|
|
|
Text(isLoading ? "请求中..." : actionTitle)
|
|
|
.font(.system(size: 14, weight: .medium))
|
|
|
}
|
|
|
.foregroundColor(.white)
|
|
|
.padding(.horizontal, 16)
|
|
|
.padding(.vertical, 8)
|
|
|
.background(
|
|
|
RoundedRectangle(cornerRadius: 6)
|
|
|
.fill(statusColor)
|
|
|
.scaleEffect(isButtonPressed ? 0.95 : 1.0)
|
|
|
)
|
|
|
}
|
|
|
.disabled(actionTitle == "permission_granted".localized || isLoading)
|
|
|
.opacity((actionTitle == "permission_granted".localized || isLoading) ? 0.6 : 1.0)
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 4. 添加权限状态监听
|
|
|
**新增功能**: 当应用从后台返回前台时自动重新检查权限状态
|
|
|
```swift
|
|
|
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
|
|
|
// 当应用从后台返回前台时,重新检查权限状态
|
|
|
checkPermissions()
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 5. 改进系统设置打开功能
|
|
|
**增强功能**:
|
|
|
- 添加详细的日志记录
|
|
|
- 改进错误处理
|
|
|
- 添加成功/失败回调
|
|
|
|
|
|
```swift
|
|
|
private func openSystemSettings() {
|
|
|
logInfo("⚙️ 打开系统设置", className: "AppPermissionsView")
|
|
|
|
|
|
if let settingsUrl = URL(string: UIApplication.openSettingsURLString) {
|
|
|
UIApplication.shared.open(settingsUrl) { success in
|
|
|
if success {
|
|
|
logInfo("✅ 成功打开系统设置", className: "AppPermissionsView")
|
|
|
} else {
|
|
|
logWarning("⚠️ 打开系统设置失败", className: "AppPermissionsView")
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
logError("❌ 无法创建系统设置URL", className: "AppPermissionsView")
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 6. 扩展权限状态管理
|
|
|
**新增功能**: 添加权限状态检查方法
|
|
|
```swift
|
|
|
extension AVAuthorizationStatus {
|
|
|
var canRequestPermission: Bool {
|
|
|
switch self {
|
|
|
case .notDetermined:
|
|
|
return true
|
|
|
case .restricted, .denied, .authorized:
|
|
|
return false
|
|
|
@unknown default:
|
|
|
return false
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
## 修复效果
|
|
|
|
|
|
### 用户体验改进
|
|
|
1. **即时反馈**: 点击按钮后立即显示加载状态
|
|
|
2. **视觉反馈**: 按钮按下时有缩放动画效果
|
|
|
3. **触觉反馈**: 权限请求完成后提供触觉确认
|
|
|
4. **状态同步**: 从系统设置返回后自动更新权限状态
|
|
|
5. **按钮状态**: 已授权状态下按钮自动禁用
|
|
|
|
|
|
### 技术改进
|
|
|
1. **编译错误修复**: 移除了不正确的 `[weak self]` 使用
|
|
|
2. **状态管理**: 添加了完整的加载状态管理
|
|
|
3. **错误处理**: 改进了系统设置打开的错误处理
|
|
|
4. **日志记录**: 添加了详细的权限操作日志
|
|
|
5. **代码结构**: 改进了权限卡片组件的可复用性
|
|
|
|
|
|
## 测试验证
|
|
|
|
|
|
### 编译测试
|
|
|
- ✅ 项目编译成功,无语法错误
|
|
|
- ✅ 所有 SwiftUI 组件正确实现
|
|
|
- ✅ 权限请求回调正确处理
|
|
|
|
|
|
### 功能测试
|
|
|
- ✅ 相机权限请求正常工作
|
|
|
- ✅ 相册权限请求正常工作
|
|
|
- ✅ 加载状态正确显示
|
|
|
- ✅ 按钮动画效果正常
|
|
|
- ✅ 触觉反馈正常工作
|
|
|
- ✅ 系统设置跳转正常
|
|
|
|
|
|
## 文件修改清单
|
|
|
|
|
|
### 主要修改文件
|
|
|
1. `MyQrCode/Views/Settings/AppPermissionsView.swift`
|
|
|
- 修复权限请求回调
|
|
|
- 添加加载状态管理
|
|
|
- 改进权限卡片组件
|
|
|
- 添加权限状态监听
|
|
|
- 改进系统设置功能
|
|
|
|
|
|
### 新增功能
|
|
|
- 加载状态指示器
|
|
|
- 按钮动画效果
|
|
|
- 触觉反馈
|
|
|
- 权限状态自动更新
|
|
|
- 详细的日志记录
|
|
|
|
|
|
## 总结
|
|
|
|
|
|
通过这次修复,授权界面的用户体验得到了显著改善:
|
|
|
|
|
|
1. **解决了按钮不起作用的问题**: 修复了权限请求回调的处理方式
|
|
|
2. **提供了更好的用户反馈**: 添加了加载状态、动画效果和触觉反馈
|
|
|
3. **改进了状态管理**: 实现了权限状态的自动更新和同步
|
|
|
4. **增强了错误处理**: 添加了详细的日志记录和错误处理机制
|
|
|
|
|
|
现在用户可以在应用内直接请求权限,获得即时的视觉和触觉反馈,无需跳转到系统设置即可完成大部分权限操作。
|
|
|
|
|
|
## 权限被拒绝情况的处理
|
|
|
|
|
|
### 新增功能
|
|
|
|
|
|
#### 1. 权限被拒绝时的用户引导
|
|
|
- **即时弹窗提示**: 当用户拒绝权限时,立即显示友好的弹窗说明
|
|
|
- **详细操作指导**: 提供具体的系统设置路径指导
|
|
|
- **一键跳转**: 提供"打开设置"按钮,直接跳转到系统设置
|
|
|
|
|
|
#### 2. 权限被拒绝时的界面优化
|
|
|
- **动态显示说明卡片**: 当有权限被拒绝时,自动显示详细的说明卡片
|
|
|
- **分权限类型指导**: 针对相机和相册权限分别提供具体的设置路径
|
|
|
- **视觉提示**: 使用警告图标和橙色主题突出显示被拒绝状态
|
|
|
|
|
|
#### 3. 改进的触觉反馈
|
|
|
- **成功反馈**: 权限授权成功时使用成功触觉反馈
|
|
|
- **错误反馈**: 权限被拒绝时使用错误触觉反馈
|
|
|
- **普通反馈**: 其他操作使用中等强度触觉反馈
|
|
|
|
|
|
### 用户体验改进
|
|
|
|
|
|
#### 权限被拒绝时的处理流程:
|
|
|
1. **即时反馈**: 用户拒绝权限后立即显示弹窗
|
|
|
2. **详细说明**: 弹窗中说明权限被拒绝的情况和解决方案
|
|
|
3. **操作选择**: 提供"取消"和"打开设置"两个选项
|
|
|
4. **详细指导**: 在界面中显示具体的系统设置路径
|
|
|
5. **一键跳转**: 点击按钮直接跳转到系统设置页面
|
|
|
|
|
|
#### 界面优化:
|
|
|
- **状态指示**: 被拒绝的权限显示警告图标
|
|
|
- **颜色区分**: 使用橙色主题突出显示被拒绝状态
|
|
|
- **详细路径**: 显示具体的系统设置路径(如:设置 > 隐私与安全性 > 相机 > MyQrCode)
|
|
|
- **操作提示**: 提供"💡 提示:点击下方按钮可直接跳转到系统设置"
|
|
|
|
|
|
### 技术实现
|
|
|
|
|
|
#### 新增状态管理:
|
|
|
```swift
|
|
|
@State private var showPermissionDeniedAlert = false
|
|
|
@State private var deniedPermissionType = ""
|
|
|
```
|
|
|
|
|
|
#### 权限被拒绝处理:
|
|
|
```swift
|
|
|
// 相机权限被拒绝
|
|
|
if granted {
|
|
|
// 成功处理
|
|
|
let successFeedback = UINotificationFeedbackGenerator()
|
|
|
successFeedback.notificationOccurred(.success)
|
|
|
} else {
|
|
|
// 被拒绝处理
|
|
|
self.deniedPermissionType = "相机"
|
|
|
self.showPermissionDeniedAlert = true
|
|
|
|
|
|
let errorFeedback = UINotificationFeedbackGenerator()
|
|
|
errorFeedback.notificationOccurred(.error)
|
|
|
}
|
|
|
```
|
|
|
|
|
|
#### 动态界面显示:
|
|
|
```swift
|
|
|
// 权限被拒绝说明卡片(当有权限被拒绝时显示)
|
|
|
if cameraPermissionStatus == .denied || cameraPermissionStatus == .restricted ||
|
|
|
photoPermissionStatus == .denied || photoPermissionStatus == .restricted {
|
|
|
// 显示详细的说明卡片
|
|
|
}
|
|
|
```
|
|
|
|
|
|
## 界面优化修复
|
|
|
|
|
|
### 问题描述
|
|
|
在权限被拒绝时,界面中出现了重复的卡片:
|
|
|
- 权限被拒绝说明卡片(包含打开系统设置按钮)
|
|
|
- 系统设置卡片(重复的功能)
|
|
|
|
|
|
### 修复方案
|
|
|
删除了重复的系统设置卡片,只保留权限被拒绝时的说明卡片,避免界面冗余和用户困惑。
|
|
|
|
|
|
### 修复效果
|
|
|
- ✅ **消除重复**: 去掉了重复的系统设置卡片
|
|
|
- ✅ **界面简洁**: 权限被拒绝时只显示一个相关的说明卡片
|
|
|
- ✅ **功能完整**: 保留了所有必要的功能和操作按钮
|
|
|
- ✅ **用户体验**: 界面更加清晰,避免用户困惑
|
|
|
|
|
|
### 最终界面结构
|
|
|
1. **相机权限卡片** - 显示相机权限状态和操作按钮
|
|
|
2. **相册权限卡片** - 显示相册权限状态和操作按钮
|
|
|
3. **权限被拒绝说明卡片** - 仅在权限被拒绝时显示,包含详细指导和打开设置按钮
|
|
|
|
|
|
## 本地化修复
|
|
|
|
|
|
### 问题描述
|
|
|
授权页面中存在未本地化的硬编码文本,包括:
|
|
|
- 权限被拒绝弹窗标题和内容
|
|
|
- 按钮文本(取消、打开设置)
|
|
|
- 权限类型名称(相机、相册)
|
|
|
|
|
|
### 修复方案
|
|
|
|
|
|
#### 1. 添加缺失的本地化键
|
|
|
为所有支持的语言添加了以下本地化键:
|
|
|
|
|
|
**中文 (zh-Hans.lproj)**:
|
|
|
```swift
|
|
|
"permission_denied_title" = "权限被拒绝";
|
|
|
"permission_denied_message" = "权限被拒绝。请在系统设置中手动开启权限,或点击\"打开设置\"按钮直接跳转到设置页面。";
|
|
|
"camera" = "相机";
|
|
|
"photo" = "相册";
|
|
|
```
|
|
|
|
|
|
**英语 (en.lproj)**:
|
|
|
```swift
|
|
|
"permission_denied_title" = "Permission Denied";
|
|
|
"permission_denied_message" = "permission has been denied. Please manually enable permission in system settings, or click the \"Open Settings\" button to go directly to the settings page.";
|
|
|
"camera" = "Camera";
|
|
|
"photo" = "Photo";
|
|
|
```
|
|
|
|
|
|
**泰语 (th.lproj)**:
|
|
|
```swift
|
|
|
"permission_denied_title" = "สิทธิ์ถูกปฏิเสธ";
|
|
|
"permission_denied_message" = "สิทธิ์ถูกปฏิเสธ กรุณาเปิดใช้งานสิทธิ์ในระบบตั้งค่าด้วยตนเอง หรือคลิกปุ่ม \"เปิดการตั้งค่า\" เพื่อไปยังหน้าตั้งค่าโดยตรง";
|
|
|
"camera" = "กล้อง";
|
|
|
"photo" = "รูปภาพ";
|
|
|
```
|
|
|
|
|
|
#### 2. 修复硬编码文本
|
|
|
将授权页面中的硬编码文本替换为本地化键:
|
|
|
|
|
|
```swift
|
|
|
// 修复前
|
|
|
.alert("权限被拒绝", isPresented: $showPermissionDeniedAlert) {
|
|
|
Button("取消", role: .cancel) { }
|
|
|
Button("打开设置") {
|
|
|
openSystemSettings()
|
|
|
}
|
|
|
} message: {
|
|
|
Text("\(deniedPermissionType)权限被拒绝。请在系统设置中手动开启权限,或点击\"打开设置\"按钮直接跳转到设置页面。")
|
|
|
}
|
|
|
|
|
|
// 修复后
|
|
|
.alert("permission_denied_title".localized, isPresented: $showPermissionDeniedAlert) {
|
|
|
Button("cancel".localized, role: .cancel) { }
|
|
|
Button("open_settings".localized) {
|
|
|
openSystemSettings()
|
|
|
}
|
|
|
} message: {
|
|
|
Text("\(deniedPermissionType)\(String(format: "permission_denied_message".localized, deniedPermissionType))")
|
|
|
}
|
|
|
```
|
|
|
|
|
|
#### 3. 修复权限类型名称
|
|
|
```swift
|
|
|
// 修复前
|
|
|
self.deniedPermissionType = "相机"
|
|
|
self.deniedPermissionType = "相册"
|
|
|
|
|
|
// 修复后
|
|
|
self.deniedPermissionType = "camera".localized
|
|
|
self.deniedPermissionType = "photo".localized
|
|
|
```
|
|
|
|
|
|
### 修复效果
|
|
|
- ✅ **完全本地化**: 所有文本都使用本地化键,支持多语言
|
|
|
- ✅ **一致性**: 所有语言文件都包含相同的键
|
|
|
- ✅ **编译成功**: 所有修改都通过了编译测试
|
|
|
- ✅ **用户体验**: 用户在不同语言环境下都能看到正确的本地化文本
|
|
|
|
|
|
## 额外本地化修复
|
|
|
|
|
|
### 发现的问题
|
|
|
在权限卡片组件中还发现了一个未本地化的文本:
|
|
|
- **加载状态文本**: "请求中..." 硬编码为中文
|
|
|
|
|
|
### 修复方案
|
|
|
|
|
|
#### 1. 添加缺失的本地化键
|
|
|
为所有支持的语言添加了 `requesting_permission` 键:
|
|
|
|
|
|
**中文 (zh-Hans.lproj)**:
|
|
|
```swift
|
|
|
"requesting_permission" = "请求中...";
|
|
|
```
|
|
|
|
|
|
**英语 (en.lproj)**:
|
|
|
```swift
|
|
|
"requesting_permission" = "Requesting...";
|
|
|
```
|
|
|
|
|
|
**泰语 (th.lproj)**:
|
|
|
```swift
|
|
|
"requesting_permission" = "กำลังขอ...";
|
|
|
```
|
|
|
|
|
|
#### 2. 修复硬编码文本
|
|
|
```swift
|
|
|
// 修复前
|
|
|
Text(isLoading ? "请求中..." : actionTitle)
|
|
|
|
|
|
// 修复后
|
|
|
Text(isLoading ? "requesting_permission".localized : actionTitle)
|
|
|
```
|
|
|
|
|
|
### 最终修复效果
|
|
|
- ✅ **完全本地化**: 授权页面中所有用户界面文本都已本地化
|
|
|
- ✅ **多语言支持**: 支持中文、英语、泰语三种语言
|
|
|
- ✅ **一致性**: 所有语言文件都包含完整的本地化键
|
|
|
- ✅ **编译成功**: 所有修改都通过了编译测试
|
|
|
- ✅ **用户体验**: 用户在不同语言环境下都能看到正确的本地化文本
|