You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
MyQRCode/MyQrCode/Views/CreateQRCodeView.swift.backup

642 lines
25 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import SwiftUI
import CoreData
import CoreImage
// MARK: - 二维码创建界面
struct CreateQRCodeView: View {
@Environment(\.dismiss) private var dismiss
@StateObject private var coreDataManager = CoreDataManager.shared
// 从类型选择界面传入的参数
let selectedQRCodeType: QRCodeType
@State private var content = ""
@State private var showingAlert = false
@State private var alertMessage = ""
// 输入焦点,确保进入页面自动弹出键盘
@FocusState private var isContentFieldFocused: Bool
// Email相关字段
@State private var emailAddress = ""
@State private var emailSubject = ""
@State private var emailBody = ""
@State private var emailCc = ""
@State private var emailBcc = ""
@FocusState private var focusedEmailField: EmailField?
// Email字段枚举
private enum EmailField: Hashable {
case address, subject, body, cc, bcc
}
var body: some View {
VStack(spacing: 0) {
inputAndPreviewSection
}
.navigationTitle(selectedQRCodeType.displayName)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("创建") { createQRCode() }
.disabled(!canCreateQRCode())
.font(.system(size: 16, weight: .semibold))
}
}
.alert("提示", isPresented: $showingAlert) {
Button("确定") { }
} message: { Text(alertMessage) }
.onAppear {
// 稍延迟以确保进入页面时自动聚焦
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
isContentFieldFocused = true
}
}
.onTapGesture {
// 点击外部关闭键盘
isContentFieldFocused = false
}
}
// MARK: - UI Components
private var emailInputSection: some View {
VStack(spacing: 16) {
// Email地址 (必填)
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("邮箱地址")
.font(.subheadline)
.foregroundColor(.primary)
Text("*")
.foregroundColor(.red)
Spacer()
}
TextField("user@example.com", text: $emailAddress)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.emailAddress)
.autocapitalization(.none)
.focused($focusedEmailField, equals: .address)
}
// 主题 (必填)
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("主题")
.font(.subheadline)
.foregroundColor(.primary)
Text("*")
.foregroundColor(.red)
Spacer()
}
TextField("邮件主题", text: $emailSubject)
.textFieldStyle(RoundedBorderTextFieldStyle())
.focused($focusedEmailField, equals: .subject)
}
// 正文 (必填)
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("正文")
.font(.subheadline)
.foregroundColor(.primary)
Text("*")
.foregroundColor(.red)
Spacer()
}
ZStack {
TextEditor(text: $emailBody)
.frame(minHeight: 120)
.padding(8)
.background(Color(.systemBackground))
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(focusedEmailField == .body ? Color.blue : Color(.systemGray4), lineWidth: 1)
)
.focused($focusedEmailField, equals: .body)
.onChange(of: emailBody) { newValue in
// 限制最大字符数为1200
if newValue.count > 1200 {
emailBody = String(newValue.prefix(1200))
}
}
// 占位符文本
if emailBody.isEmpty && focusedEmailField != .body {
VStack {
HStack {
Text("输入邮件正文内容...")
.foregroundColor(.secondary)
.font(.body)
Spacer()
}
Spacer()
}
.padding(16)
.allowsHitTesting(false)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
}
}
// 字符计数
HStack {
Spacer()
Text("\(emailBody.count)/1200")
.font(.caption)
.foregroundColor(emailBody.count >= 1200 ? .orange : .secondary)
}
}
// CC地址 (可选)
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("抄送地址")
.font(.subheadline)
.foregroundColor(.primary)
Spacer()
}
TextField("cc@example.com", text: $emailCc)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.emailAddress)
.autocapitalization(.none)
.focused($focusedEmailField, equals: .cc)
}
// BCC地址 (可选)
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("密送地址")
.font(.subheadline)
.foregroundColor(.primary)
Spacer()
}
TextField("bcc@example.com", text: $emailBcc)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.emailAddress)
.autocapitalization(.none)
.focused($focusedEmailField, equals: .bcc)
}
}
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("完成") {
focusedEmailField = nil
}
.foregroundColor(.blue)
.font(.system(size: 16, weight: .medium))
}
}
}
private var inputAndPreviewSection: some View {
ScrollView {
VStack(spacing: 24) {
// 输入提示
VStack(spacing: 12) {
HStack {
Image(systemName: "info.circle")
.font(.caption)
.foregroundColor(.blue)
Text(getContentHint())
.font(.caption)
.foregroundColor(.secondary)
.lineLimit(nil)
Spacer()
}
.padding(.horizontal, 12)
.padding(.vertical, 8)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color.blue.opacity(0.1))
)
}
.padding(.horizontal, 20)
// 内容输入区域
VStack(spacing: 16) {
HStack {
Text(selectedQRCodeType == .mail ? "邮件信息" : "输入内容")
.font(.headline)
.foregroundColor(.primary)
Spacer()
}
if selectedQRCodeType == .mail {
// Email专用输入界面
emailInputSection
} else {
// 通用输入界面
VStack(spacing: 8) {
ZStack {
// 输入框主体
TextEditor(text: $content)
.frame(minHeight: 120)
.padding(8)
.background(Color(.systemBackground))
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(isContentFieldFocused ? Color.blue : Color(.systemGray4), lineWidth: 1)
)
.focused($isContentFieldFocused)
.onChange(of: content) { newValue in
// 限制最大字符数为150
if newValue.count > 150 {
content = String(newValue.prefix(150))
}
}
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("完成") {
isContentFieldFocused = false
}
.foregroundColor(.blue)
.font(.system(size: 16, weight: .medium))
}
}
// 占位符文本 - 左上角对齐
if content.isEmpty && !isContentFieldFocused {
VStack {
HStack {
Text(getPlaceholderText())
.foregroundColor(.secondary)
.font(.body)
Spacer()
}
Spacer()
}
.padding(16)
.allowsHitTesting(false)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
}
}
// 字符计数和限制提示 - 输入框底下
HStack {
Spacer()
VStack(alignment: .trailing, spacing: 4) {
// 字符限制提示
if content.count >= 150 {
HStack(spacing: 4) {
Image(systemName: "exclamationmark.triangle")
.font(.caption)
.foregroundColor(.orange)
Text("已达到最大字符数")
.font(.caption)
.foregroundColor(.orange)
}
} else if content.count >= 140 {
HStack(spacing: 4) {
Image(systemName: "info.circle")
.font(.caption)
.foregroundColor(.blue)
Text("接近字符限制")
.font(.caption)
.foregroundColor(.blue)
}
}
// 字符计数
Text("\(content.count)/150")
.font(.caption)
.foregroundColor(getCharacterCountColor())
}
}
}
}
.padding(.horizontal, 20)
// 预览区域
if !content.isEmpty {
VStack(spacing: 16) {
HStack {
Text("预览")
.font(.headline)
.foregroundColor(.primary)
Spacer()
Button(action: {
// 可以添加分享功能
}) {
Image(systemName: "square.and.arrow.up")
.font(.system(size: 16))
.foregroundColor(.blue)
}
}
VStack(spacing: 16) {
// 二维码图片
if let qrCodeImage = generateQRCodeImage() {
Image(uiImage: qrCodeImage)
.interpolation(.none)
.resizable()
.scaledToFit()
.frame(width: 200, height: 200)
.background(Color.white)
.cornerRadius(12)
.shadow(color: .black.opacity(0.1), radius: 8, x: 0, y: 4)
}
// 内容预览卡片
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("内容")
.font(.caption)
.foregroundColor(.secondary)
Spacer()
Text(selectedQRCodeType.displayName)
.font(.caption)
.padding(.horizontal, 6)
.padding(.vertical, 2)
.background(Color.orange.opacity(0.1))
.foregroundColor(.orange)
.cornerRadius(4)
}
Text(formatContentForQRCodeType())
.font(.body)
.foregroundColor(.primary)
.textSelection(.enabled)
}
.padding()
.background(Color(.systemGray6))
.cornerRadius(8)
}
.padding()
.background(Color(.systemBackground))
.cornerRadius(12)
.shadow(color: .black.opacity(0.05), radius: 4, x: 0, y: 2)
}
.padding(.horizontal, 20)
}
Spacer(minLength: 100)
}
.padding(.top, 20)
}
.background(Color(.systemGroupedBackground))
}
// MARK: - Helper Methods
private func canCreateQRCode() -> Bool {
switch selectedQRCodeType {
case .mail:
// Email类型邮箱地址、主题、正文为必填
return !emailAddress.isEmpty && !emailSubject.isEmpty && !emailBody.isEmpty
default:
// 其他类型:内容不能为空
return !content.isEmpty
}
}
private func getCharacterCountColor() -> Color {
if content.count >= 150 {
return .orange
} else if content.count >= 140 {
return .blue
} else {
return .secondary
}
}
private func getContentHint() -> String {
switch selectedQRCodeType {
case .text:
return "输入任意文本内容"
case .url:
return "输入网址https://www.example.com"
case .mail:
return "输入邮箱地址user@example.com"
case .phone:
return "输入电话号码,如:+86 138 0013 8000"
case .sms:
return "输入短信内容Hello World"
case .wifi:
return "输入WiFi信息SSID:MyWiFi,Password:12345678"
case .vcard:
return "输入联系人信息"
case .mecard:
return "输入联系人信息(简化版)"
case .location:
return "输入地理位置40.7128,-74.0060"
case .calendar:
return "输入日历事件信息"
case .instagram:
return "输入Instagram用户名或链接"
case .facebook:
return "输入Facebook用户名或链接"
case .spotify:
return "输入Spotify歌曲或播放列表链接"
case .twitter:
return "输入Twitter用户名或链接"
case .whatsapp:
return "输入WhatsApp消息内容"
case .viber:
return "输入Viber消息内容"
case .snapchat:
return "输入Snapchat用户名"
case .tiktok:
return "输入TikTok用户名或链接"
}
}
private func getPlaceholderText() -> String {
switch selectedQRCodeType {
case .text:
return "输入文本内容"
case .url:
return "输入网址"
case .mail:
return "输入邮箱地址"
case .phone:
return "输入电话号码"
case .sms:
return "输入短信内容"
case .wifi:
return "输入WiFi信息"
case .vcard:
return "输入联系人信息"
case .mecard:
return "输入联系人信息"
case .location:
return "输入地理位置坐标"
case .calendar:
return "输入日历事件信息"
case .instagram:
return "输入Instagram信息"
case .facebook:
return "输入Facebook信息"
case .spotify:
return "输入Spotify链接"
case .twitter:
return "输入Twitter信息"
case .whatsapp:
return "输入WhatsApp消息"
case .viber:
return "输入Viber消息"
case .snapchat:
return "输入Snapchat用户名"
case .tiktok:
return "输入TikTok信息"
}
}
private func generateQRCodeImage() -> UIImage? {
// 根据二维码类型检查内容是否为空
let hasContent: Bool
switch selectedQRCodeType {
case .mail:
hasContent = !emailAddress.isEmpty && !emailSubject.isEmpty && !emailBody.isEmpty
default:
hasContent = !content.isEmpty
}
guard hasContent else { return nil }
// 根据二维码类型格式化内容
let formattedContent = formatContentForQRCodeType()
// 生成二维码
let data = formattedContent.data(using: .utf8)
let qrFilter = CIFilter.qrCodeGenerator()
qrFilter.setValue(data, forKey: "inputMessage")
qrFilter.setValue("H", forKey: "inputCorrectionLevel") // 高纠错级别
guard let outputImage = qrFilter.outputImage else { return nil }
// 转换为UIImage
let context = CIContext()
guard let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else { return nil }
return UIImage(cgImage: cgImage)
}
private func formatContentForQRCodeType() -> String {
switch selectedQRCodeType {
case .text:
return content
case .url:
return content.hasPrefix("http") ? content : "https://\(content)"
case .mail:
var mailtoURL = "mailto:\(emailAddress)"
var queryParams: [String] = []
if !emailSubject.isEmpty {
queryParams.append("subject=\(emailSubject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? emailSubject)")
}
if !emailBody.isEmpty {
queryParams.append("body=\(emailBody.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? emailBody)")
}
if !emailCc.isEmpty {
queryParams.append("cc=\(emailCc)")
}
if !emailBcc.isEmpty {
queryParams.append("bcc=\(emailBcc)")
}
if !queryParams.isEmpty {
mailtoURL += "?" + queryParams.joined(separator: "&")
}
return mailtoURL
case .phone:
return "tel:\(content)"
case .sms:
return "sms:\(content)"
case .wifi:
return "WIFI:T:WPA;S:\(content);P:password;;"
case .vcard:
return "BEGIN:VCARD\nVERSION:3.0\nFN:\(content)\nEND:VCARD"
case .mecard:
return "MECARD:N:\(content);;"
case .location:
return "geo:\(content)"
case .calendar:
return "BEGIN:VEVENT\nSUMMARY:\(content)\nEND:VEVENT"
case .instagram:
return "https://instagram.com/\(content)"
case .facebook:
return "https://facebook.com/\(content)"
case .spotify:
return content.hasPrefix("http") ? content : "https://open.spotify.com/track/\(content)"
case .twitter:
return "https://twitter.com/\(content)"
case .whatsapp:
return "https://wa.me/\(content)"
case .viber:
return "viber://chat?number=\(content)"
case .snapchat:
return "https://snapchat.com/add/\(content)"
case .tiktok:
return "https://tiktok.com/@\(content)"
}
}
private func createQRCode() {
let context = 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 = selectedQRCodeType.rawValue
// 根据类型设置内容
switch selectedQRCodeType {
case .mail:
historyItem.content = "邮箱: \(emailAddress)\n主题: \(emailSubject)\n正文: \(emailBody)"
if !emailCc.isEmpty {
historyItem.content += "\n抄送: \(emailCc)"
}
if !emailBcc.isEmpty {
historyItem.content += "\n密送: \(emailBcc)"
}
default:
historyItem.content = content
}
do {
try context.save()
alertMessage = "二维码创建成功!"
showingAlert = true
// 创建成功后返回
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
dismiss()
}
} catch {
alertMessage = "保存失败:\(error.localizedDescription)"
showingAlert = true
}
}
}
#Preview {
NavigationView {
CreateQRCodeView(selectedQRCodeType: .text)
}
}