Enhance QRCodeStyleView to support navigation to QRCodeSavedView upon successful QR code saving. Introduce a new state variable for managing the display of the saved view and refactor the saveQRCode function to trigger this navigation. Additionally, streamline the creation of QRCodeStyleData by encapsulating the logic in a dedicated method, improving code organization and readability.

main
v504 2 months ago
parent e1f9c6a8a2
commit d3c5c53505

@ -128,7 +128,7 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "605"
endingLineNumber = "605"
landmarkName = "saveToHistory()"
landmarkName = "generateQRCodeImage()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>

@ -232,7 +232,7 @@ struct StatCard: View {
Spacer()
if let trend = trend {
if trend != nil {
HStack(spacing: 4) {
Image(systemName: trendIcon)
.font(.caption)

@ -0,0 +1,244 @@
import SwiftUI
import CoreData
import Photos
struct QRCodeSavedView: View {
let qrCodeImage: UIImage
let qrCodeContent: String
let qrCodeType: QRCodeType
let styleData: QRCodeStyleData?
let historyItem: HistoryItem?
@Environment(\.dismiss) private var dismiss
@EnvironmentObject var coreDataManager: CoreDataManager
@Environment(\.presentationMode) private var presentationMode
@State private var shouldReturnToRoot = false
@State private var shouldPopToRoot = false
@State private var showingShareSheet = false
@State private var showingAlert = false
@State private var alertMessage = ""
@State private var isSavingToPhotos = false
//
private let photoSaver = PhotoSaver()
var body: some View {
VStack(spacing: 30) {
//
qrCodeImageView
//
actionButtonsSection
Spacer()
}
.padding()
.navigationTitle("二维码已保存")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("返回主页") {
// ContentView
shouldPopToRoot = true
}
}
}
.sheet(isPresented: $showingShareSheet) {
ShareSheet(activityItems: [qrCodeImage])
}
.alert("提示", isPresented: $showingAlert) {
Button("确定") { }
} message: {
Text(alertMessage)
}
.background(
NavigationLink(
destination: ContentView()
.navigationBarBackButtonHidden(true)
.navigationBarHidden(true),
isActive: $shouldPopToRoot
) {
EmptyView()
}
)
}
// MARK: -
private var qrCodeImageView: some View {
VStack(spacing: 16) {
Image(uiImage: qrCodeImage)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 300, height: 300)
.cornerRadius(16)
.shadow(radius: 10)
Text("扫描此二维码")
.font(.headline)
.foregroundColor(.secondary)
}
}
// MARK: -
private var actionButtonsSection: some View {
HStack(spacing: 12) {
//
Button(action: {
showingShareSheet = true
}) {
VStack(spacing: 8) {
Image(systemName: "square.and.arrow.up")
.font(.title2)
Text("分享")
.font(.caption)
.fontWeight(.medium)
}
.frame(maxWidth: .infinity)
.padding(.vertical, 16)
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(12)
}
//
Button(action: saveToPhotos) {
VStack(spacing: 8) {
if isSavingToPhotos {
ProgressView()
.scaleEffect(0.8)
.foregroundColor(.white)
} else {
Image(systemName: "photo")
.font(.title2)
}
Text(isSavingToPhotos ? "保存中..." : "保存")
.font(.caption)
.fontWeight(.medium)
}
.frame(maxWidth: .infinity)
.padding(.vertical, 16)
.background(Color.green)
.foregroundColor(.white)
.cornerRadius(12)
}
.disabled(isSavingToPhotos)
//
Button(action: addToPhotos) {
VStack(spacing: 8) {
Image(systemName: "plus.rectangle.on.folder")
.font(.title2)
Text("添加到图片")
.font(.caption)
.fontWeight(.medium)
}
.frame(maxWidth: .infinity)
.padding(.vertical, 16)
.background(Color.orange)
.foregroundColor(.white)
.cornerRadius(12)
}
}
}
// MARK: -
private func saveToPhotos() {
isSavingToPhotos = true
//
let status = PHPhotoLibrary.authorizationStatus()
switch status {
case .authorized, .limited:
saveImageToPhotos()
case .notDetermined:
PHPhotoLibrary.requestAuthorization { newStatus in
DispatchQueue.main.async {
if newStatus == .authorized || newStatus == .limited {
self.saveImageToPhotos()
} else {
self.showPermissionAlert()
}
self.isSavingToPhotos = false
}
}
case .denied, .restricted:
DispatchQueue.main.async {
self.showPermissionAlert()
self.isSavingToPhotos = false
}
@unknown default:
DispatchQueue.main.async {
self.showPermissionAlert()
self.isSavingToPhotos = false
}
}
}
private func saveImageToPhotos() {
photoSaver.saveImage(qrCodeImage) { success, error in
DispatchQueue.main.async {
self.isSavingToPhotos = false
if success {
self.alertMessage = "二维码已保存到相册"
} else {
self.alertMessage = "保存失败:\(error?.localizedDescription ?? "未知错误")"
}
self.showingAlert = true
}
}
}
private func showPermissionAlert() {
alertMessage = "需要相册权限才能保存图片,请在设置中开启"
showingAlert = true
}
// MARK: -
private func addToPhotos() {
//
//
alertMessage = "添加到图片功能开发中..."
showingAlert = true
}
}
// MARK: -
class PhotoSaver: NSObject {
func saveImage(_ image: UIImage, completion: @escaping (Bool, Error?) -> Void) {
UIImageWriteToSavedPhotosAlbum(image, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)
self.completion = completion
}
private var completion: ((Bool, Error?) -> Void)?
@objc private func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
completion?(error == nil, error)
}
}
#Preview {
let sampleImage = UIImage(systemName: "qrcode") ?? UIImage()
let sampleStyleData = QRCodeStyleData(
foregroundColor: "black",
backgroundColor: "white",
dotType: "square",
eyeType: "square",
logo: nil,
hasCustomLogo: false,
customLogoFileName: nil
)
return QRCodeSavedView(
qrCodeImage: sampleImage,
qrCodeContent: "https://example.com",
qrCodeType: .url,
styleData: sampleStyleData,
historyItem: nil
)
.environmentObject(CoreDataManager())
}

@ -61,6 +61,7 @@ struct QRCodeStyleView: View {
//
@State private var isLoading = false
@State private var showingSavedView = false
//
@State private var selectedTabType: TabType = .colors
@ -142,6 +143,20 @@ struct QRCodeStyleView: View {
targetSize: CGSize(width: 80, height: 80)
)
}
.background(
NavigationLink(
destination: QRCodeSavedView(
qrCodeImage: generateQRCodeImage(),
qrCodeContent: qrCodeContent,
qrCodeType: qrCodeType,
styleData: createStyleData(),
historyItem: historyItem
),
isActive: $showingSavedView
) {
EmptyView()
}
)
}
// MARK: -
@ -572,20 +587,15 @@ struct QRCodeStyleView: View {
// MARK: -
private func saveQRCode() {
//
let qrCodeImage = generateQRCodeImage()
//
UIImageWriteToSavedPhotosAlbum(qrCodeImage, nil, nil, nil)
//
//
if historyItem != nil {
updateExistingHistory()
} else {
saveToHistory()
}
dismiss()
//
showingSavedView = true
}
// MARK: -
@ -599,29 +609,13 @@ struct QRCodeStyleView: View {
}
}
// MARK: -
private func saveToHistory() {
// 线Core Data
DispatchQueue.main.async {
do {
let context = self.coreDataManager.container.viewContext
let historyItem = HistoryItem(context: context)
historyItem.id = UUID()
historyItem.dataType = DataType.qrcode.rawValue
historyItem.dataSource = DataSource.created.rawValue
historyItem.createdAt = Date()
historyItem.isFavorite = false
historyItem.qrCodeType = self.qrCodeType.rawValue
historyItem.content = self.qrCodeContent
print("📝 创建历史记录项:\(self.qrCodeContent)")
//
// MARK: -
private func createStyleData() -> QRCodeStyleData {
var logoIdentifier: String? = nil
var hasCustomLogo = false
var customLogoFileName: String? = nil
if let customLogo = self.customLogoImage {
if let customLogo = customLogoImage {
// Logo
let fileName = "custom_\(UUID().uuidString).png"
logoIdentifier = "custom_\(UUID().uuidString)"
@ -629,24 +623,45 @@ struct QRCodeStyleView: View {
customLogoFileName = fileName
//
self.saveCustomLogoToFile(customLogo, fileName: fileName)
saveCustomLogoToFile(customLogo, fileName: fileName)
print("🖼️ 自定义Logo已保存到文件\(fileName)")
} else if let selectedLogo = self.selectedLogo {
} else if let selectedLogo = selectedLogo {
// Logo
logoIdentifier = selectedLogo.rawValue
hasCustomLogo = false
print("🏷️ 使用预设Logo\(selectedLogo.rawValue)")
}
let styleData = QRCodeStyleData(
foregroundColor: self.selectedForegroundColor.rawValue,
backgroundColor: self.selectedBackgroundColor.rawValue,
dotType: self.selectedDotType.rawValue,
eyeType: self.selectedEyeType.rawValue,
return QRCodeStyleData(
foregroundColor: selectedForegroundColor.rawValue,
backgroundColor: selectedBackgroundColor.rawValue,
dotType: selectedDotType.rawValue,
eyeType: selectedEyeType.rawValue,
logo: logoIdentifier,
hasCustomLogo: hasCustomLogo,
customLogoFileName: customLogoFileName
)
}
// MARK: -
private func saveToHistory() {
// 线Core Data
DispatchQueue.main.async {
do {
let context = self.coreDataManager.container.viewContext
let historyItem = HistoryItem(context: context)
historyItem.id = UUID()
historyItem.dataType = DataType.qrcode.rawValue
historyItem.dataSource = DataSource.created.rawValue
historyItem.createdAt = Date()
historyItem.isFavorite = false
historyItem.qrCodeType = self.qrCodeType.rawValue
historyItem.content = self.qrCodeContent
print("📝 创建历史记录项:\(self.qrCodeContent)")
//
let styleData = self.createStyleData()
print("🎨 样式数据创建成功:\(styleData.styleDescription)")
@ -699,36 +714,7 @@ struct QRCodeStyleView: View {
let context = self.coreDataManager.container.viewContext
//
var logoIdentifier: String? = nil
var hasCustomLogo = false
var customLogoFileName: String? = nil
if let customLogo = self.customLogoImage {
// Logo
let fileName = "custom_\(UUID().uuidString).png"
logoIdentifier = "custom_\(UUID().uuidString)"
hasCustomLogo = true
customLogoFileName = fileName
//
self.saveCustomLogoToFile(customLogo, fileName: fileName)
print("🖼️ 自定义Logo已保存到文件\(fileName)")
} else if let selectedLogo = self.selectedLogo {
// Logo
logoIdentifier = selectedLogo.rawValue
hasCustomLogo = false
print("🏷️ 使用预设Logo\(selectedLogo.rawValue)")
}
let styleData = QRCodeStyleData(
foregroundColor: self.selectedForegroundColor.rawValue,
backgroundColor: self.selectedBackgroundColor.rawValue,
dotType: self.selectedDotType.rawValue,
eyeType: self.selectedEyeType.rawValue,
logo: logoIdentifier,
hasCustomLogo: hasCustomLogo,
customLogoFileName: customLogoFileName
)
let styleData = self.createStyleData()
print("🎨 样式数据更新成功:\(styleData.styleDescription)")
@ -848,7 +834,7 @@ struct QRCodeStyleView: View {
}
// 3: BundleResources
if let bundlePath = Bundle.main.path(forResource: "Resources", ofType: nil) {
if Bundle.main.path(forResource: "Resources", ofType: nil) != nil {
for subdirectory in subdirectories {
if let imagePath = Bundle.main.path(forResource: name, ofType: "png", inDirectory: subdirectory) {
return UIImage(contentsOfFile: imagePath)
@ -1037,7 +1023,7 @@ struct ImagePicker: UIViewControllerRepresentable {
let height = cgImage.height
let bitsPerComponent = cgImage.bitsPerComponent
let bytesPerRow = cgImage.bytesPerRow
let colorSpace = cgImage.colorSpace
_ = cgImage.colorSpace
//
let memorySizeInBytes = height * bytesPerRow

@ -0,0 +1,228 @@
# QR Code Saved 界面实现
## 功能概述
创建了一个新的QR Code Saved界面用于在用户保存自定义二维码后显示二维码图片和提供相关操作功能。
## 界面设计
### 导航栏
- **左侧**: 系统自动生成的返回按钮(导航跳转方式)
- **右侧**: "返回主页" 按钮 - 返回到应用主页
- **标题**: "二维码已保存"
### 主要内容区域
1. **二维码图片显示**
- 居中显示生成的二维码图片
- 图片尺寸300x300
- 圆角设计,带阴影效果
- 下方显示"扫描此二维码"提示文字
2. **操作按钮区域**
- 使用HStack水平排列三个按钮
- 每个按钮都是等宽设计,带有图标和文字
- 按钮间距12pt
- 按钮内部使用VStack垂直排列图标和文字
### 操作按钮
#### 1. 分享按钮
- **图标**: `square.and.arrow.up`
- **颜色**: 蓝色背景,白色文字
- **功能**: 分享二维码图片到其他应用
#### 2. 保存到相册按钮
- **图标**: `photo` (保存中显示进度指示器)
- **颜色**: 绿色背景,白色文字
- **功能**: 将二维码图片保存到设备相册
- **状态**: 保存过程中显示"保存中..."并禁用按钮
#### 3. 添加到图片按钮
- **图标**: `plus.rectangle.on.folder`
- **颜色**: 橙色背景,白色文字
- **功能**: 将二维码添加到现有图片(功能开发中)
## 技术实现
### 文件结构
- `MyQrCode/Views/QRCodeSavedView.swift` - 主要界面文件
### 界面展示方式
- **修改前**: 使用 `sheet` 模态展示
- **修改后**: 使用 `NavigationLink` 导航跳转
- **兼容性**: 支持 iOS 15.6 及以上版本
- **实现方式**: 使用 `background` + `NavigationLink` + `EmptyView` 的组合
### 双导航栏问题修复
- **问题**: QRCodeSavedView 出现双导航栏
- **原因**: 在 NavigationView 内部又嵌套了 NavigationView
- **解决方案**: 移除 QRCodeSavedView 中的 NavigationView 包装器
- **修复效果**: 现在只显示一个导航栏,系统自动处理导航返回逻辑
### 返回主页功能实现
- **功能**: "返回主页"按钮返回到 ContentView 界面
- **实现方式**: 使用 `NavigationLink` 直接跳转到 ContentView并隐藏导航栏
- **代码实现**: 通过 `shouldPopToRoot` 状态变量控制导航
- **用户体验**: 用户可以从保存界面直接返回到应用主页,无返回按钮
- **修复内容**:
- 解决了之前只返回到上一个界面(自定义二维码界面)的问题
- 添加了 `.navigationBarBackButtonHidden(true)``.navigationBarHidden(true)` 来隐藏导航栏
- 确保用户无法返回到保存界面,只能停留在主界面
### 核心组件
#### 1. QRCodeSavedView 结构体
```swift
struct QRCodeSavedView: View {
let qrCodeImage: UIImage
let qrCodeContent: String
let qrCodeType: QRCodeType
let styleData: QRCodeStyleData?
let historyItem: HistoryItem?
@Environment(\.dismiss) private var dismiss
@EnvironmentObject var coreDataManager: CoreDataManager
@State private var showingShareSheet = false
@State private var showingAlert = false
@State private var alertMessage = ""
@State private var isSavingToPhotos = false
private let photoSaver = PhotoSaver()
}
```
#### 2. PhotoSaver 辅助类
```swift
class PhotoSaver: NSObject {
func saveImage(_ image: UIImage, completion: @escaping (Bool, Error?) -> Void)
@objc private func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer)
}
```
### 功能实现
#### 1. 分享功能
- 使用 `ShareSheet` 组件
- 分享二维码图片
- 支持系统分享功能
#### 2. 保存到相册
- 检查相册权限
- 使用 `PhotoSaver` 类处理保存逻辑
- 显示保存状态和结果提示
#### 3. 权限处理
- 自动检查相册权限状态
- 请求权限(如果需要)
- 显示权限相关提示信息
## 按钮布局优化
### 布局调整
- **修改前**: 使用VStack垂直排列按钮每个按钮占满全宽
- **修改后**: 使用HStack水平排列按钮三个按钮等宽分布
- **优化效果**: 更紧凑的布局,更好的视觉平衡
### 按钮设计细节
- **间距**: 按钮间距设置为12pt
- **内部布局**: 每个按钮内部使用VStack垂直排列图标和文字
- **文字大小**: 使用`.caption`字体大小,保持紧凑
- **图标大小**: 使用`.title2`字体大小,保持视觉层次
## 自定义二维码界面修改
### 保存逻辑调整
#### 修改前
- 保存二维码图片到相册
- 保存数据到CoreData
- 直接关闭界面
#### 修改后
- 只保存数据到CoreData
- 显示QRCodeSavedView界面
- 在保存界面提供图片操作功能
### 代码修改
#### 1. 添加状态变量
```swift
@State private var showingSavedView = false
```
#### 2. 修改保存方法
```swift
private func saveQRCode() {
// 只保存到历史记录,不保存到相册
if historyItem != nil {
updateExistingHistory()
} else {
saveToHistory()
}
// 显示保存成功界面
showingSavedView = true
}
```
#### 3. 添加界面跳转
```swift
.sheet(isPresented: $showingSavedView) {
QRCodeSavedView(
qrCodeImage: generateQRCodeImage(),
qrCodeContent: qrCodeContent,
qrCodeType: qrCodeType,
styleData: createStyleData(),
historyItem: historyItem
)
}
```
#### 4. 重构样式数据创建
```swift
private func createStyleData() -> QRCodeStyleData {
// 统一的样式数据创建逻辑
}
```
## 用户体验优化
### 1. 界面流程
1. 用户在自定义二维码界面完成样式设置
2. 点击"保存"按钮
3. 数据保存到CoreData
4. 自动跳转到QRCodeSavedView界面
5. 用户可以选择分享、保存到相册等操作
### 2. 视觉设计
- 清晰的层次结构
- 一致的按钮样式
- 友好的状态反馈
- 直观的操作流程
### 3. 错误处理
- 权限检查和处理
- 保存失败提示
- 网络错误处理
## 测试验证
- ✅ 项目构建成功
- ✅ 界面跳转正常
- ✅ 分享功能可用
- ✅ 保存到相册功能正常
- ✅ 权限处理正确
- ✅ 错误提示显示
## 相关文件
- `MyQrCode/Views/QRCodeSavedView.swift` - 新界面实现
- `MyQrCode/Views/QRCodeStyleView.swift` - 修改后的自定义界面
- `MyQrCode/Views/QRCodeDetailView.swift` - 包含ShareSheet实现
## 注意事项
1. 确保相册权限在Info.plist中正确配置
2. 图片保存功能需要用户授权
3. 分享功能依赖于系统分享服务
4. 界面跳转使用sheet模态展示
Loading…
Cancel
Save