Enhance CreateQRCodeView to support navigation to QRCodeStyleView upon QR code creation; implement comprehensive QR code content generation for various types including mail, WiFi, vCard, MeCard, location, calendar, and social media. This update improves user experience by streamlining the QR code creation process and expanding functionality.

main
v504 2 months ago
parent de38e46f2e
commit 9abb38594b

@ -0,0 +1,20 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -0,0 +1,402 @@
import SwiftUI
import QRCode
// MARK: -
enum QRCodeColor: String, CaseIterable, Hashable {
case black = "black"
case white = "white"
case red = "red"
case blue = "blue"
case green = "green"
case yellow = "yellow"
case purple = "purple"
case orange = "orange"
case pink = "pink"
case cyan = "cyan"
case magenta = "magenta"
case brown = "brown"
case gray = "gray"
case navy = "navy"
case teal = "teal"
case indigo = "indigo"
case lime = "lime"
case maroon = "maroon"
case olive = "olive"
case silver = "silver"
//
case gradientRed = "gradientRed"
case gradientBlue = "gradientBlue"
case gradientGreen = "gradientGreen"
case gradientPurple = "gradientPurple"
case gradientOrange = "gradientOrange"
case gradientPink = "gradientPink"
case gradientYellow = "gradientYellow"
case gradientCyan = "gradientCyan"
case gradientMagenta = "gradientMagenta"
case gradientTeal = "gradientTeal"
case gradientIndigo = "gradientIndigo"
case gradientLime = "gradientLime"
var color: Color {
switch self {
case .black: return Color(red: 0, green: 0, blue: 0)
case .white: return Color(red: 1, green: 1, blue: 1)
case .red: return Color(red: 1, green: 0, blue: 0)
case .blue: return Color(red: 0, green: 0, blue: 1)
case .green: return Color(red: 0, green: 1, blue: 0)
case .yellow: return Color(red: 1, green: 1, blue: 0)
case .purple: return Color(red: 0.5, green: 0, blue: 0.5)
case .orange: return Color(red: 1, green: 0.5, blue: 0)
case .pink: return Color(red: 1, green: 0.75, blue: 0.8)
case .cyan: return Color(red: 0, green: 1, blue: 1)
case .magenta: return Color(red: 1, green: 0, blue: 1)
case .brown: return Color(red: 0.6, green: 0.4, blue: 0.2)
case .gray: return Color(red: 0.5, green: 0.5, blue: 0.5)
case .navy: return Color(red: 0, green: 0, blue: 0.5)
case .teal: return Color(red: 0, green: 0.5, blue: 0.5)
case .indigo: return Color(red: 0.3, green: 0, blue: 0.5)
case .lime: return Color(red: 0.5, green: 1, blue: 0)
case .maroon: return Color(red: 0.5, green: 0, blue: 0)
case .olive: return Color(red: 0.5, green: 0.5, blue: 0)
case .silver: return Color(red: 0.75, green: 0.75, blue: 0.75)
case .gradientRed: return Color(red: 1, green: 0, blue: 0)
case .gradientBlue: return Color(red: 0, green: 0, blue: 1)
case .gradientGreen: return Color(red: 0, green: 1, blue: 0)
case .gradientPurple: return Color(red: 0.5, green: 0, blue: 0.5)
case .gradientOrange: return Color(red: 1, green: 0.5, blue: 0)
case .gradientPink: return Color(red: 1, green: 0.75, blue: 0.8)
case .gradientYellow: return Color(red: 1, green: 1, blue: 0)
case .gradientCyan: return Color(red: 0, green: 1, blue: 1)
case .gradientMagenta: return Color(red: 1, green: 0, blue: 1)
case .gradientTeal: return Color(red: 0, green: 0.5, blue: 0.5)
case .gradientIndigo: return Color(red: 0.3, green: 0, blue: 0.5)
case .gradientLime: return Color(red: 0.5, green: 1, blue: 0)
}
}
var cgColor: CGColor {
return color.cgColor!
}
static var foregroundColors: [QRCodeColor] {
return [.black, .red, .blue, .green, .purple, .orange, .pink, .brown, .navy, .teal, .indigo, .maroon, .olive, .gradientRed, .gradientBlue, .gradientGreen, .gradientPurple, .gradientOrange, .gradientPink, .gradientYellow, .gradientCyan, .gradientMagenta, .gradientTeal, .gradientIndigo, .gradientLime]
}
static var backgroundColors: [QRCodeColor] {
return [.white, .gray, .silver, .cyan, .yellow, .lime]
}
}
// MARK: -
enum QRCodeDotType: String, CaseIterable, Hashable {
case square = "data_square"
case circle = "data_circle"
case roundedRect = "data_roundedRect"
case squircle = "data_squircle"
case diamond = "data_diamond"
case hexagon = "data_hexagon"
case star = "data_star"
case heart = "data_heart"
case flower = "data_flower"
case gear = "data_gear"
case abstract = "data_abstract"
case arrow = "data_arrow"
case blob = "data_blob"
case circuit = "data_circuit"
case crosshatch = "data_crosshatch"
case crt = "data_crt"
case curvePixel = "data_curvePixel"
case diagonal = "data_diagonal"
case diagonalStripes = "data_diagonalStripes"
case donut = "data_donut"
case dripHorizontal = "data_dripHorizontal"
case dripVertical = "data_dripVertical"
case flame = "data_flame"
case grid2x2 = "data_grid2x2"
case grid3x3 = "data_grid3x3"
case grid4x4 = "data_grid4x4"
case horizontal = "data_horizontal"
case koala = "data_koala"
case pointy = "data_pointy"
case razor = "data_razor"
case roundedEndIndent = "data_roundedEndIndent"
case roundedPath = "data_roundedPath"
case roundedTriangle = "data_roundedTriangle"
case sharp = "data_sharp"
case shiny = "data_shiny"
case spikyCircle = "data_spikyCircle"
case stitch = "data_stitch"
case vertical = "data_vertical"
case vortex = "data_vortex"
case wave = "data_wave"
case wex = "data_wex"
var thumbnailName: String {
return rawValue
}
var displayName: String {
switch self {
case .square: return "方形"
case .circle: return "圆形"
case .roundedRect: return "圆角矩形"
case .squircle: return "超椭圆"
case .diamond: return "菱形"
case .hexagon: return "六边形"
case .star: return "星形"
case .heart: return "心形"
case .flower: return "花朵"
case .gear: return "齿轮"
case .abstract: return "抽象"
case .arrow: return "箭头"
case .blob: return "斑点"
case .circuit: return "电路"
case .crosshatch: return "交叉线"
case .crt: return "CRT"
case .curvePixel: return "曲线像素"
case .diagonal: return "对角线"
case .diagonalStripes: return "对角条纹"
case .donut: return "甜甜圈"
case .dripHorizontal: return "水平滴落"
case .dripVertical: return "垂直滴落"
case .flame: return "火焰"
case .grid2x2: return "2x2网格"
case .grid3x3: return "3x3网格"
case .grid4x4: return "4x4网格"
case .horizontal: return "水平"
case .koala: return "考拉"
case .pointy: return "尖角"
case .razor: return "剃刀"
case .roundedEndIndent: return "圆角缩进"
case .roundedPath: return "圆角路径"
case .roundedTriangle: return "圆角三角形"
case .sharp: return "尖锐"
case .shiny: return "闪亮"
case .spikyCircle: return "尖刺圆形"
case .stitch: return "缝合"
case .vertical: return "垂直"
case .vortex: return "漩涡"
case .wave: return "波浪"
case .wex: return "WEX"
}
}
var pixelShape: QRCodePixelShapeGenerator {
switch self {
case .square: return QRCode.PixelShape.Square()
case .circle: return QRCode.PixelShape.Circle()
case .roundedRect: return QRCode.PixelShape.RoundedRect()
case .squircle: return QRCode.PixelShape.Squircle()
case .diamond: return QRCode.PixelShape.Diamond()
case .hexagon: return QRCode.PixelShape.Hexagon()
case .star: return QRCode.PixelShape.Star()
case .heart: return QRCode.PixelShape.Heart()
case .flower: return QRCode.PixelShape.Flower()
case .gear: return QRCode.PixelShape.Gear()
case .abstract: return QRCode.PixelShape.Abstract()
case .arrow: return QRCode.PixelShape.Arrow()
case .blob: return QRCode.PixelShape.Blob()
case .circuit: return QRCode.PixelShape.Circuit()
case .crosshatch: return QRCode.PixelShape.Crosshatch()
case .crt: return QRCode.PixelShape.CRT()
case .curvePixel: return QRCode.PixelShape.CurvePixel()
case .diagonal: return QRCode.PixelShape.Diagonal()
case .diagonalStripes: return QRCode.PixelShape.DiagonalStripes()
case .donut: return QRCode.PixelShape.Donut()
case .dripHorizontal: return QRCode.PixelShape.DripHorizontal()
case .dripVertical: return QRCode.PixelShape.DripVertical()
case .flame: return QRCode.PixelShape.Flame()
case .grid2x2: return QRCode.PixelShape.Grid2x2()
case .grid3x3: return QRCode.PixelShape.Grid3x3()
case .grid4x4: return QRCode.PixelShape.Grid4x4()
case .horizontal: return QRCode.PixelShape.Horizontal()
case .koala: return QRCode.PixelShape.Koala()
case .pointy: return QRCode.PixelShape.Pointy()
case .razor: return QRCode.PixelShape.Razor()
case .roundedEndIndent: return QRCode.PixelShape.RoundedEndIndent()
case .roundedPath: return QRCode.PixelShape.RoundedPath()
case .roundedTriangle: return QRCode.PixelShape.RoundedTriangle()
case .sharp: return QRCode.PixelShape.Sharp()
case .shiny: return QRCode.PixelShape.Shiny()
case .spikyCircle: return QRCode.PixelShape.SpikyCircle()
case .stitch: return QRCode.PixelShape.Stitch()
case .vertical: return QRCode.PixelShape.Vertical()
case .vortex: return QRCode.PixelShape.Vortex()
case .wave: return QRCode.PixelShape.Wave()
case .wex: return QRCode.PixelShape.Wex()
}
}
}
// MARK: -
enum QRCodeEyeType: String, CaseIterable, Hashable {
case square = "eye_square"
case circle = "eye_circle"
case roundedRect = "eye_roundedRect"
case squircle = "eye_squircle"
case arc = "eye_arc"
case barsHorizontal = "eye_barsHorizontal"
case barsVertical = "eye_barsVertical"
case cloud = "eye_cloud"
case cloudCircle = "eye_cloudCircle"
case corneredPixels = "eye_corneredPixels"
case crt = "eye_crt"
case diagonalStripes = "eye_diagonalStripes"
case dotDragHorizontal = "eye_dotDragHorizontal"
case dotDragVertical = "eye_dotDragVertical"
case edges = "eye_edges"
case explode = "eye_explode"
case eye = "eye_eye"
case fabricScissors = "eye_fabricScissors"
case fireball = "eye_fireball"
case flame = "eye_flame"
case headlight = "eye_headlight"
case holePunch = "eye_holePunch"
case leaf = "eye_leaf"
case peacock = "eye_peacock"
case pinch = "eye_pinch"
case pixels = "eye_pixels"
case roundedOuter = "eye_roundedOuter"
case roundedPointingIn = "eye_roundedPointingIn"
case roundedPointingOut = "eye_roundedPointingOut"
case shield = "eye_shield"
case spikyCircle = "eye_spikyCircle"
case squarePeg = "eye_squarePeg"
case surroundingBars = "eye_surroundingBars"
case teardrop = "eye_teardrop"
case ufo = "eye_ufo"
case ufoRounded = "eye_ufoRounded"
case usePixelShape = "eye_usePixelShape"
var thumbnailName: String {
return rawValue
}
var displayName: String {
switch self {
case .square: return "方形"
case .circle: return "圆形"
case .roundedRect: return "圆角矩形"
case .squircle: return "超椭圆"
case .arc: return "弧形"
case .barsHorizontal: return "水平条"
case .barsVertical: return "垂直条"
case .cloud: return "云朵"
case .cloudCircle: return "云朵圆形"
case .corneredPixels: return "角像素"
case .crt: return "CRT"
case .diagonalStripes: return "对角条纹"
case .dotDragHorizontal: return "水平拖拽点"
case .dotDragVertical: return "垂直拖拽点"
case .edges: return "边缘"
case .explode: return "爆炸"
case .eye: return "眼睛"
case .fabricScissors: return "剪刀"
case .fireball: return "火球"
case .flame: return "火焰"
case .headlight: return "头灯"
case .holePunch: return "打孔"
case .leaf: return "叶子"
case .peacock: return "孔雀"
case .pinch: return "捏合"
case .pixels: return "像素"
case .roundedOuter: return "圆角外"
case .roundedPointingIn: return "圆角向内"
case .roundedPointingOut: return "圆角向外"
case .shield: return "盾牌"
case .spikyCircle: return "尖刺圆形"
case .squarePeg: return "方形钉"
case .surroundingBars: return "环绕条"
case .teardrop: return "泪滴"
case .ufo: return "UFO"
case .ufoRounded: return "圆角UFO"
case .usePixelShape: return "使用像素形状"
}
}
var eyeShape: QRCodeEyeShapeGenerator {
switch self {
case .square: return QRCode.EyeShape.Square()
case .circle: return QRCode.EyeShape.Circle()
case .roundedRect: return QRCode.EyeShape.RoundedRect()
case .squircle: return QRCode.EyeShape.Squircle()
case .arc: return QRCode.EyeShape.Arc()
case .barsHorizontal: return QRCode.EyeShape.BarsHorizontal()
case .barsVertical: return QRCode.EyeShape.BarsVertical()
case .cloud: return QRCode.EyeShape.Cloud()
case .cloudCircle: return QRCode.EyeShape.CloudCircle()
case .corneredPixels: return QRCode.EyeShape.CorneredPixels()
case .crt: return QRCode.EyeShape.CRT()
case .diagonalStripes: return QRCode.EyeShape.DiagonalStripes()
case .dotDragHorizontal: return QRCode.EyeShape.DotDragHorizontal()
case .dotDragVertical: return QRCode.EyeShape.DotDragVertical()
case .edges: return QRCode.EyeShape.Edges()
case .explode: return QRCode.EyeShape.Explode()
case .eye: return QRCode.EyeShape.Eye()
case .fabricScissors: return QRCode.EyeShape.FabricScissors()
case .fireball: return QRCode.EyeShape.Fireball()
case .flame: return QRCode.EyeShape.Flame()
case .headlight: return QRCode.EyeShape.Headlight()
case .holePunch: return QRCode.EyeShape.HolePunch()
case .leaf: return QRCode.EyeShape.Leaf()
case .peacock: return QRCode.EyeShape.Peacock()
case .pinch: return QRCode.EyeShape.Pinch()
case .pixels: return QRCode.EyeShape.Pixels()
case .roundedOuter: return QRCode.EyeShape.RoundedOuter()
case .roundedPointingIn: return QRCode.EyeShape.RoundedPointingIn()
case .roundedPointingOut: return QRCode.EyeShape.RoundedPointingOut()
case .shield: return QRCode.EyeShape.Shield()
case .spikyCircle: return QRCode.EyeShape.SpikyCircle()
case .squarePeg: return QRCode.EyeShape.SquarePeg()
case .surroundingBars: return QRCode.EyeShape.SurroundingBars()
case .teardrop: return QRCode.EyeShape.Teardrop()
case .ufo: return QRCode.EyeShape.UFO()
case .ufoRounded: return QRCode.EyeShape.UFORounded()
case .usePixelShape: return QRCode.EyeShape.UsePixelShape()
}
}
}
// MARK: - Logo
enum QRCodeLogo: String, CaseIterable, Hashable {
case scanMe = "scanMe"
case gmail = "gmail"
case paypal = "paypal"
case googlePlaystore = "googlePlaystore"
case spotify = "spotify"
case telegram = "telegram"
case whatsApp = "whatsApp"
case linkedIn = "linkedIn"
case tikTok = "tikTok"
case snapchat = "snapchat"
case youtube = "youtube"
case x = "x"
case pinterest = "pinterest"
case instagram = "instagram"
case facebook = "facebook"
var thumbnailName: String {
return rawValue
}
var displayName: String {
switch self {
case .scanMe: return "扫描我"
case .gmail: return "Gmail"
case .paypal: return "PayPal"
case .googlePlaystore: return "Google Play"
case .spotify: return "Spotify"
case .telegram: return "Telegram"
case .whatsApp: return "WhatsApp"
case .linkedIn: return "LinkedIn"
case .tikTok: return "TikTok"
case .snapchat: return "Snapchat"
case .youtube: return "YouTube"
case .x: return "X"
case .pinterest: return "Pinterest"
case .instagram: return "Instagram"
case .facebook: return "Facebook"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

@ -73,6 +73,7 @@ struct CreateQRCodeView: View {
//
@State private var showingAlert = false
@State private var alertMessage = ""
@State private var navigateToStyleView = false
var body: some View {
VStack(spacing: 0) {
@ -82,14 +83,26 @@ struct CreateQRCodeView: View {
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("创建") { createQRCode() }
.disabled(!canCreateQRCode())
.font(.system(size: 16, weight: .semibold))
Button("创建") {
if canCreateQRCode() {
navigateToStyleView = true
}
}
.disabled(!canCreateQRCode())
.font(.system(size: 16, weight: .semibold))
}
}
.alert("提示", isPresented: $showingAlert) {
Button("确定") { }
} message: { Text(alertMessage) }
.background(
NavigationLink(
destination: QRCodeStyleView(qrCodeContent: generateQRCodeContent()),
isActive: $navigateToStyleView
) {
EmptyView()
}
)
.onAppear {
setupInitialFocus()
}
@ -632,6 +645,129 @@ struct CreateQRCodeView: View {
}
}
// MARK: -
private func generateQRCodeContent() -> String {
switch selectedQRCodeType {
case .mail:
var mailContent = "mailto:\(emailAddress)"
if !emailSubject.isEmpty || !emailBody.isEmpty || !emailCc.isEmpty || !emailBcc.isEmpty {
mailContent += "?"
var params: [String] = []
if !emailSubject.isEmpty {
params.append("subject=\(emailSubject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")")
}
if !emailBody.isEmpty {
params.append("body=\(emailBody.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")")
}
if !emailCc.isEmpty {
params.append("cc=\(emailCc.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")")
}
if !emailBcc.isEmpty {
params.append("bcc=\(emailBcc.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")")
}
mailContent += params.joined(separator: "&")
}
return mailContent
case .wifi:
var wifiContent = "WIFI:"
wifiContent += "S:\(wifiSSID);"
wifiContent += "T:\(wifiEncryptionType.rawValue.uppercased());"
if !wifiPassword.isEmpty {
wifiContent += "P:\(wifiPassword);"
}
wifiContent += ";"
return wifiContent
case .vcard:
var vcardContent = "BEGIN:VCARD\nVERSION:3.0\n"
if !contactFirstName.isEmpty || !contactLastName.isEmpty {
vcardContent += "N:\(contactLastName);\(contactFirstName);;;\n"
vcardContent += "FN:\(contactFirstName) \(contactLastName)\n"
}
if !contactPhone.isEmpty {
vcardContent += "TEL;TYPE=WORK,PREF:\(contactPhone)\n"
}
if !contactEmail.isEmpty {
vcardContent += "EMAIL:\(contactEmail)\n"
}
if !contactCompany.isEmpty {
vcardContent += "ORG:\(contactCompany)\n"
}
if !contactTitle.isEmpty {
vcardContent += "TITLE:\(contactTitle)\n"
}
if !contactAddress.isEmpty {
vcardContent += "ADR;TYPE=WORK:;;\(contactAddress);;;;\n"
}
if !contactWebsite.isEmpty {
vcardContent += "URL:\(contactWebsite)\n"
}
vcardContent += "END:VCARD"
return vcardContent
case .mecard:
var mecardContent = "MECARD:"
if !contactFirstName.isEmpty || !contactLastName.isEmpty {
mecardContent += "N:\(contactLastName),\(contactFirstName);"
}
if !contactNickname.isEmpty {
mecardContent += "NICKNAME:\(contactNickname);"
}
if !contactPhone.isEmpty {
mecardContent += "TEL:\(contactPhone);"
}
if !contactEmail.isEmpty {
mecardContent += "EMAIL:\(contactEmail);"
}
if !contactCompany.isEmpty {
mecardContent += "ORG:\(contactCompany);"
}
if !contactTitle.isEmpty {
mecardContent += "TITLE:\(contactTitle);"
}
if !contactAddress.isEmpty {
mecardContent += "ADR:,,\(contactAddress);"
}
if !contactWebsite.isEmpty {
mecardContent += "URL:\(contactWebsite);"
}
if !contactNote.isEmpty {
mecardContent += "NOTE:\(contactNote);"
}
mecardContent += ";"
return mecardContent
case .location:
return "geo:\(locationLatitude),\(locationLongitude)"
case .calendar:
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'"
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
let startDateString = dateFormatter.string(from: startDate)
let endDateString = dateFormatter.string(from: endDate)
var icalContent = "BEGIN:VEVENT\n"
icalContent += "SUMMARY:\(eventTitle)\n"
if !eventDescription.isEmpty {
icalContent += "DESCRIPTION:\(eventDescription)\n"
}
if !eventLocation.isEmpty {
icalContent += "LOCATION:\(eventLocation)\n"
}
icalContent += "DTSTART:\(startDateString)\n"
icalContent += "DTEND:\(endDateString)\n"
icalContent += "END:VEVENT"
return icalContent
case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok, .whatsapp, .viber:
return generateSocialMediaContent()
case .phone:
return "tel:\(phoneNumber)"
case .sms:
return "SMSTO:\(phoneNumber)"
case .url:
return urlString
default:
return content
}
}
// MARK: - Facebook ID
private func extractFacebookId(from input: String) -> String {
// Facebook/ID

@ -0,0 +1,396 @@
import SwiftUI
import QRCode
import CoreData
// MARK: -
struct QRCodeStyleView: View {
let qrCodeContent: String
@Environment(\.dismiss) private var dismiss
@StateObject private var coreDataManager = CoreDataManager.shared
//
@State private var selectedForegroundColor: QRCodeColor = .black
@State private var selectedBackgroundColor: QRCodeColor = .white
//
@State private var selectedDotType: QRCodeDotType = .square
//
@State private var selectedEyeType: QRCodeEyeType = .square
// Logo
@State private var selectedLogo: QRCodeLogo? = nil
//
@State private var qrCodeImage: UIImage?
@State private var isLoading = false
// QRCode
private func createQRCodeDocument() -> QRCode.Document {
let d = try! QRCode.Document(engine: QRCodeEngineExternal())
// 使
d.utf8String = qrCodeContent
//
d.design.backgroundColor(selectedBackgroundColor.cgColor)
//
d.design.style.eye = QRCode.FillStyle.Solid(selectedForegroundColor.cgColor)
d.design.style.eyeBackground = selectedBackgroundColor.cgColor
//
d.design.shape.onPixels = selectedDotType.pixelShape
d.design.style.onPixels = QRCode.FillStyle.Solid(selectedForegroundColor.cgColor)
d.design.style.onPixelsBackground = selectedBackgroundColor.cgColor
d.design.shape.offPixels = selectedDotType.pixelShape
d.design.style.offPixels = QRCode.FillStyle.Solid(selectedBackgroundColor.cgColor)
d.design.style.offPixelsBackground = selectedBackgroundColor.cgColor
//
d.design.shape.eye = selectedEyeType.eyeShape
// Logo
if let selectedLogo = selectedLogo {
// Logo
// d.design.style.background = QRCode.FillStyle.Image(selectedLogo.image)
}
return d
}
var body: some View {
VStack(spacing: 0) {
//
qrCodePreviewSection
//
styleSelectionSection
}
.navigationTitle("自定义样式")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("保存") {
saveQRCode()
}
.font(.system(size: 16, weight: .semibold))
}
}
.onAppear {
}
}
// MARK: -
private var qrCodePreviewSection: some View {
VStack(spacing: 16) {
QRCodeDocumentUIView(document: createQRCodeDocument())
.frame(width: 300, height: 300)
}
.padding()
.background(Color(.systemBackground))
}
// MARK: -
private var styleSelectionSection: some View {
ScrollView {
VStack(spacing: 24) {
//
colorSelectionSection(
title: "前景色",
colors: QRCodeColor.foregroundColors,
selectedColor: $selectedForegroundColor
)
//
colorSelectionSection(
title: "背景色",
colors: QRCodeColor.backgroundColors,
selectedColor: $selectedBackgroundColor
)
//
dotTypeSelectionSection
//
eyeTypeSelectionSection
// Logo
logoSelectionSection
}
.padding()
}
.background(Color(.systemGroupedBackground))
}
// MARK: -
private func colorSelectionSection(
title: String,
colors: [QRCodeColor],
selectedColor: Binding<QRCodeColor>
) -> some View {
VStack(alignment: .leading, spacing: 12) {
Text(title)
.font(.headline)
.foregroundColor(.primary)
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 6), spacing: 12) {
ForEach(colors, id: \.self) { color in
Button(action: {
selectedColor.wrappedValue = color
}) {
RoundedRectangle(cornerRadius: 8)
.fill(color.color)
.frame(height: 40)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(selectedColor.wrappedValue == color ? Color.blue : Color.clear, lineWidth: 3)
)
}
}
}
}
}
// MARK: -
private var dotTypeSelectionSection: some View {
VStack(alignment: .leading, spacing: 12) {
Text("点类型")
.font(.headline)
.foregroundColor(.primary)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12) {
ForEach(QRCodeDotType.allCases, id: \.self) { dotType in
Button(action: {
selectedDotType = dotType
}) {
VStack(spacing: 8) {
if let image = loadImage(named: dotType.thumbnailName) {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 40, height: 40)
.background(Color.white)
.cornerRadius(8)
} else {
RoundedRectangle(cornerRadius: 8)
.fill(Color.gray.opacity(0.3))
.frame(width: 40, height: 40)
.overlay(
Text("?")
.font(.caption)
.foregroundColor(.secondary)
)
}
Text(dotType.displayName)
.font(.caption)
.foregroundColor(.primary)
}
.padding(8)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(selectedDotType == dotType ? Color.blue.opacity(0.1) : Color.clear)
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(selectedDotType == dotType ? Color.blue : Color.clear, lineWidth: 2)
)
)
}
}
}
.padding(.horizontal)
}
}
}
// MARK: -
private var eyeTypeSelectionSection: some View {
VStack(alignment: .leading, spacing: 12) {
Text("眼睛类型")
.font(.headline)
.foregroundColor(.primary)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12) {
ForEach(QRCodeEyeType.allCases, id: \.self) { eyeType in
Button(action: {
selectedEyeType = eyeType
}) {
VStack(spacing: 8) {
if let image = loadImage(named: eyeType.thumbnailName) {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 40, height: 40)
.background(Color.white)
.cornerRadius(8)
} else {
RoundedRectangle(cornerRadius: 8)
.fill(Color.gray.opacity(0.3))
.frame(width: 40, height: 40)
.overlay(
Text("?")
.font(.caption)
.foregroundColor(.secondary)
)
}
Text(eyeType.displayName)
.font(.caption)
.foregroundColor(.primary)
}
.padding(8)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(selectedEyeType == eyeType ? Color.blue.opacity(0.1) : Color.clear)
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(selectedEyeType == eyeType ? Color.blue : Color.clear, lineWidth: 2)
)
)
}
}
}
.padding(.horizontal)
}
}
}
// MARK: - Logo
private var logoSelectionSection: some View {
VStack(alignment: .leading, spacing: 12) {
Text("Logo")
.font(.headline)
.foregroundColor(.primary)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12) {
// Logo
Button(action: {
selectedLogo = nil
}) {
VStack(spacing: 8) {
RoundedRectangle(cornerRadius: 8)
.fill(Color.gray.opacity(0.3))
.frame(width: 40, height: 40)
.overlay(
Text("")
.font(.caption)
.foregroundColor(.secondary)
)
Text("无Logo")
.font(.caption)
.foregroundColor(.primary)
}
.padding(8)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(selectedLogo == nil ? Color.blue.opacity(0.1) : Color.clear)
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(selectedLogo == nil ? Color.blue : Color.clear, lineWidth: 2)
)
)
}
// Logo
ForEach(QRCodeLogo.allCases, id: \.self) { logo in
Button(action: {
selectedLogo = logo
}) {
VStack(spacing: 8) {
if let image = loadImage(named: logo.thumbnailName) {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 40, height: 40)
.background(Color.white)
.cornerRadius(8)
} else {
RoundedRectangle(cornerRadius: 8)
.fill(Color.gray.opacity(0.3))
.frame(width: 40, height: 40)
.overlay(
Text("?")
.font(.caption)
.foregroundColor(.secondary)
)
}
Text(logo.displayName)
.font(.caption)
.foregroundColor(.primary)
}
.padding(8)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(selectedLogo == logo ? Color.blue.opacity(0.1) : Color.clear)
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(selectedLogo == logo ? Color.blue : Color.clear, lineWidth: 2)
)
)
}
}
}
.padding(.horizontal)
}
}
}
// MARK: -
private func saveQRCode() {
guard let qrCodeImage = qrCodeImage else { return }
//
UIImageWriteToSavedPhotosAlbum(qrCodeImage, nil, nil, nil)
//
saveToHistory()
dismiss()
}
// MARK: -
private func saveToHistory() {
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 = "custom"
historyItem.content = qrCodeContent
do {
try context.save()
} catch {
print("保存到历史记录失败:\(error.localizedDescription)")
}
}
// MARK: -
private func loadImage(named name: String) -> UIImage? {
// Bundle
if let path = Bundle.main.path(forResource: name, ofType: "png", inDirectory: "Resources/dots") {
return UIImage(contentsOfFile: path)
}
if let path = Bundle.main.path(forResource: name, ofType: "png", inDirectory: "Resources/eyes") {
return UIImage(contentsOfFile: path)
}
if let path = Bundle.main.path(forResource: name, ofType: "png", inDirectory: "Resources/logos") {
return UIImage(contentsOfFile: path)
}
return nil
}
}
// MARK: -
#Preview {
QRCodeStyleView(qrCodeContent: "https://www.example.com")
}

@ -0,0 +1,240 @@
# 创建二维码界面跳转到自定义样式界面
## 概述
本文档描述了如何从创建二维码界面跳转到自定义样式界面的实现。用户现在可以在创建二维码时选择自定义样式,而不是直接生成标准二维码。
## 功能流程
### 1. 用户操作流程
```
创建二维码界面 → 填写内容 → 点击"创建"按钮 → 跳转到自定义样式界面 → 选择样式 → 保存二维码
```
### 2. 技术实现
#### **CreateQRCodeView.swift 修改**
**添加状态变量**
```swift
@State private var navigateToStyleView = false
```
**修改创建按钮行为**
```swift
Button("创建") {
if canCreateQRCode() {
navigateToStyleView = true
}
}
```
**添加NavigationLink导航**
```swift
.background(
NavigationLink(
destination: QRCodeStyleView(qrCodeContent: generateQRCodeContent()),
isActive: $navigateToStyleView
) {
EmptyView()
}
)
```
**新增二维码内容生成方法**
```swift
private func generateQRCodeContent() -> String {
switch selectedQRCodeType {
case .mail:
// 生成邮件格式内容
case .wifi:
// 生成WiFi格式内容
case .vcard:
// 生成vCard格式内容
case .mecard:
// 生成MeCard格式内容
// ... 其他类型
}
}
```
#### **QRCodeStyleView.swift 修改**
**添加CoreData支持**
```swift
import CoreData
@StateObject private var coreDataManager = CoreDataManager.shared
```
**移除NavigationView包装**
```swift
// 移除了NavigationView包装因为现在通过NavigationLink导航
var body: some View {
VStack(spacing: 0) {
// 内容...
}
.navigationTitle("自定义样式")
.navigationBarTitleDisplayMode(.inline)
// ...
}
```
**修改保存方法**
```swift
private func saveQRCode() {
guard let qrCodeImage = qrCodeImage else { return }
// 保存到相册
UIImageWriteToSavedPhotosAlbum(qrCodeImage, nil, nil, nil)
// 保存到历史记录
saveToHistory()
dismiss()
}
```
**新增历史记录保存方法**
```swift
private func saveToHistory() {
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 = "custom"
historyItem.content = qrCodeContent
do {
try context.save()
} catch {
print("保存到历史记录失败:\(error.localizedDescription)")
}
}
```
## 支持的二维码类型
### 1. 邮件 (mailto:)
- 支持收件人、主题、正文、抄送、密送
- 格式:`mailto:email?subject=xxx&body=xxx&cc=xxx&bcc=xxx`
### 2. WiFi
- 支持SSID、密码、加密类型
- 格式:`WIFI:S:SSID;T:ENCRYPTION;P:PASSWORD;;`
### 3. vCard (3.0版本)
- 支持姓名、电话、邮箱、公司、职位、地址、网站
- 格式标准vCard 3.0格式
### 4. MeCard
- 支持姓名、昵称、电话、邮箱、公司、职位、地址、网站、备注
- 格式:`MECARD:N:LastName,FirstName;NICKNAME:xxx;TEL:xxx;...;`
### 5. 位置
- 支持经纬度
- 格式:`geo:latitude,longitude`
### 6. 日历事件
- 支持事件标题、描述、位置、开始时间、结束时间
- 格式标准iCalendar格式
### 7. 社交媒体
- **Instagram**: `instagram://user?username=xxx`
- **Facebook**: `fb://profile/xxx`
- **Spotify**: `spotify:search:artist;song`
- **X (Twitter)**: `twitter://user?screen_name=xxx`
- **WhatsApp**: `whatsapp://send?phone=xxx`
- **Viber**: `viber://add?number=xxx`
- **TikTok**: `https://www.tiktok.com/@xxx`
- **Snapchat**: `https://snapchat.com/add/xxx`
### 8. 电话和短信
- **电话**: `tel:xxx`
- **短信**: `SMSTO:xxx`
### 9. URL
- 直接使用输入的URL
## 自定义样式功能
### 1. 颜色选择
- **前景色**: 20种颜色8种纯色 + 12种渐变色
- **背景色**: 10种浅色系
### 2. 点类型选择
- 40多种不同的点类型
- 包括基础形状、特殊形状、抽象形状、动态形状
### 3. 眼睛类型选择
- 40多种不同的眼睛类型
- 包括基础形状、特殊形状、动态形状
### 4. Logo选择
- 15种不同的Logo
- 包括社交媒体、通讯工具、其他服务
## 用户体验改进
### 1. 流程优化
- 用户填写内容后,点击"创建"按钮
- 通过NavigationLink导航到自定义样式界面
- 可以预览和调整二维码样式
- 最终保存到相册和历史记录
### 2. 数据持久化
- 自定义样式生成的二维码会保存到历史记录
- 类型标记为"custom"
- 可以在历史记录中查看和管理
### 3. 错误处理
- 输入验证确保内容有效
- 保存失败时的错误提示
- 网络和权限问题的处理
## 技术要点
### 1. 状态管理
- 使用`@State`管理界面状态
- 使用`@StateObject`管理CoreData
- 使用`@Environment`获取dismiss环境
### 2. 数据传递
- 通过参数传递二维码内容
- 使用NavigationLink导航到自定义样式界面
- 异步处理二维码生成
### 3. 内存管理
- 及时释放图片资源
- 避免内存泄漏
- 优化大图片的处理
## 未来扩展
### 1. 样式模板
- 预设样式模板
- 用户自定义模板保存
- 模板分享功能
### 2. 批量生成
- 支持批量生成不同样式的二维码
- 批量导出功能
### 3. 高级样式
- 更多颜色选项
- 自定义Logo上传
- 动画效果
## 总结
通过这次修改,我们实现了从创建二维码界面到自定义样式界面的完整流程。用户现在可以:
1. 在创建界面填写二维码内容
2. 点击创建按钮通过NavigationLink导航到样式界面
3. 选择喜欢的颜色、点类型、眼睛类型和Logo
4. 预览生成的二维码
5. 保存到相册和历史记录
这个实现提供了更好的用户体验,让用户能够创建个性化的二维码,同时保持了原有的功能完整性。

@ -0,0 +1,208 @@
# 自定义二维码样式界面
## 概述
`QRCodeStyleView` 是一个功能丰富的自定义二维码样式界面允许用户自定义二维码的外观包括颜色、点类型、眼睛类型和Logo。
## 功能特性
### 1. 颜色选择
- **前景色**: 8种纯色 + 12种渐变色
- **背景色**: 10种浅色系
### 2. 点类型选择
支持40多种不同的点类型包括
- 基础形状:方形、圆形、菱形、六边形等
- 特殊形状:星形、心形、花朵、齿轮等
- 抽象形状:抽象、斑点、电路、交叉线等
- 动态形状:火焰、漩涡、波浪等
### 3. 眼睛类型选择
支持40多种不同的眼睛类型包括
- 基础形状:方形、圆形、弧形等
- 特殊形状云朵、孔雀、UFO、泪滴等
- 动态形状:火焰、爆炸、火球等
### 4. Logo选择
支持15种不同的Logo
- 社交媒体Facebook、Instagram、X、TikTok等
- 通讯工具WhatsApp、Telegram、Viber等
- 其他服务Gmail、PayPal、Spotify等
## 界面布局
```
┌─────────────────────────────────────┐
│ 自定义样式 [保存] │
├─────────────────────────────────────┤
│ │
│ 二维码预览区域 │
│ │
├─────────────────────────────────────┤
│ 前景色选择 │
│ [颜色网格] │
│ │
│ 背景色选择 │
│ [颜色网格] │
│ │
│ 点类型选择 │
│ [水平滚动选择] │
│ │
│ 眼睛类型选择 │
│ [水平滚动选择] │
│ │
│ Logo选择 │
│ [水平滚动选择] │
└─────────────────────────────────────┘
```
## 技术实现
### 核心文件
1. **QRCodeStyleView.swift** - 主界面文件
2. **QRCodeStyleModels.swift** - 数据模型文件
### 数据模型
#### QRCodeColor
```swift
enum QRCodeColor: String, CaseIterable {
case black, white, red, blue, green, yellow, purple, orange, pink, cyan
case magenta, brown, gray, navy, teal, indigo, lime, maroon, olive, silver
// 渐变色
case gradientRed, gradientBlue, gradientGreen, gradientPurple, gradientOrange
case gradientPink, gradientYellow, gradientCyan, gradientMagenta, gradientTeal
case gradientIndigo, gradientLime
}
```
#### QRCodeDotType
```swift
enum QRCodeDotType: String, CaseIterable {
case square, circle, roundedRect, squircle, diamond, hexagon, star, heart
case flower, gear, abstract, arrow, blob, circuit, crosshatch, crt
case curvePixel, diagonal, diagonalStripes, donut, dripHorizontal, dripVertical
case flame, grid2x2, grid3x3, grid4x4, horizontal, koala, pointy, razor
case roundedEndIndent, roundedPath, roundedTriangle, sharp, shiny, spikyCircle
case stitch, vertical, vortex, wave, wex
}
```
#### QRCodeEyeType
```swift
enum QRCodeEyeType: String, CaseIterable {
case square, circle, roundedRect, squircle, arc, barsHorizontal, barsVertical
case cloud, cloudCircle, corneredPixels, crt, diagonalStripes, dotDragHorizontal
case dotDragVertical, edges, explode, eye, fabricScissors, fireball, flame
case headlight, holePunch, leaf, peacock, pinch, pixels, roundedOuter
case roundedPointingIn, roundedPointingOut, shield, spikyCircle, squarePeg
case surroundingBars, teardrop, ufo, ufoRounded, usePixelShape
}
```
#### QRCodeLogo
```swift
enum QRCodeLogo: String, CaseIterable {
case scanMe, gmail, paypal, googlePlaystore, spotify, telegram, whatsApp
case linkedIn, tikTok, snapchat, youtube, x, pinterest, instagram, facebook
}
```
### 二维码生成
使用Core Image的`CIFilter.qrCodeGenerator()`生成基础二维码:
```swift
let context = CIContext()
let filter = CIFilter.qrCodeGenerator()
filter.message = Data(qrCodeContent.utf8)
filter.correctionLevel = "M"
if let outputImage = filter.outputImage {
let scaleX = 600 / outputImage.extent.width
let scaleY = 600 / outputImage.extent.height
let transform = CGAffineTransform(scaleX: scaleX, y: scaleY)
let scaledImage = outputImage.transformed(by: transform)
if let cgImage = context.createCGImage(scaledImage, from: scaledImage.extent) {
let image = UIImage(cgImage: cgImage)
}
}
```
### 图片资源
所有样式预览图片存储在`Resources/`目录下:
- `Resources/dots/` - 点类型预览图片
- `Resources/eyes/` - 眼睛类型预览图片
- `Resources/logos/` - Logo预览图片
## 使用方法
### 1. 创建界面
```swift
QRCodeStyleView(qrCodeContent: "https://www.example.com")
```
### 2. 自定义样式
用户可以通过以下方式自定义二维码:
- 选择前景色和背景色
- 选择点类型样式
- 选择眼睛类型样式
- 选择Logo可选
### 3. 保存二维码
点击右上角的"保存"按钮将生成的二维码保存到相册。
## 界面特性
### 响应式设计
- 支持不同屏幕尺寸
- 自适应布局
- 流畅的滚动体验
### 实时预览
- 选择样式后立即生成预览
- 异步生成避免界面卡顿
- 加载状态指示
### 用户友好
- 直观的图标选择
- 清晰的分类展示
- 中文界面支持
## 扩展性
### 添加新颜色
在`QRCodeColor`枚举中添加新的颜色类型。
### 添加新点类型
1. 在`QRCodeDotType`枚举中添加新类型
2. 在`Resources/dots/`目录添加预览图片
3. 更新`displayName`和`pixelShape`属性
### 添加新眼睛类型
1. 在`QRCodeEyeType`枚举中添加新类型
2. 在`Resources/eyes/`目录添加预览图片
3. 更新`displayName`和`eyeShape`属性
### 添加新Logo
1. 在`QRCodeLogo`枚举中添加新类型
2. 在`Resources/logos/`目录添加Logo图片
3. 更新`displayName`属性
## 注意事项
1. **图片资源**: 确保所有预览图片都已添加到Xcode项目中
2. **性能优化**: 二维码生成在后台线程进行,避免界面卡顿
3. **内存管理**: 及时释放不需要的图片资源
4. **错误处理**: 处理图片加载失败的情况
## 未来改进
1. **更多样式**: 添加更多颜色、形状和Logo选项
2. **自定义Logo**: 允许用户上传自定义Logo
3. **样式预设**: 提供预设的样式组合
4. **导出选项**: 支持不同格式和尺寸的导出
5. **动画效果**: 添加选择时的动画效果
Loading…
Cancel
Save