diff --git a/MyQrCode/Assets.xcassets/Resources.imageset/Contents.json b/MyQrCode/Assets.xcassets/Resources.imageset/Contents.json new file mode 100644 index 0000000..a19a549 --- /dev/null +++ b/MyQrCode/Assets.xcassets/Resources.imageset/Contents.json @@ -0,0 +1,20 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MyQrCode/Models/QRCodeStyleModels.swift b/MyQrCode/Models/QRCodeStyleModels.swift new file mode 100644 index 0000000..649f231 --- /dev/null +++ b/MyQrCode/Models/QRCodeStyleModels.swift @@ -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" + } + } +} diff --git a/MyQrCode/Resources/dots/data_abstract.png b/MyQrCode/Resources/dots/data_abstract.png new file mode 100644 index 0000000..84bfaf5 Binary files /dev/null and b/MyQrCode/Resources/dots/data_abstract.png differ diff --git a/MyQrCode/Resources/dots/data_crosshatch.png b/MyQrCode/Resources/dots/data_crosshatch.png new file mode 100644 index 0000000..fa18675 Binary files /dev/null and b/MyQrCode/Resources/dots/data_crosshatch.png differ diff --git a/MyQrCode/Resources/dots/data_crt.png b/MyQrCode/Resources/dots/data_crt.png new file mode 100644 index 0000000..05db06d Binary files /dev/null and b/MyQrCode/Resources/dots/data_crt.png differ diff --git a/MyQrCode/Resources/dots/data_flower.png b/MyQrCode/Resources/dots/data_flower.png new file mode 100644 index 0000000..1ee73bc Binary files /dev/null and b/MyQrCode/Resources/dots/data_flower.png differ diff --git a/MyQrCode/Resources/dots/data_grid3x3.png b/MyQrCode/Resources/dots/data_grid3x3.png new file mode 100644 index 0000000..c56e7a4 Binary files /dev/null and b/MyQrCode/Resources/dots/data_grid3x3.png differ diff --git a/MyQrCode/Resources/dots/data_pointy.png b/MyQrCode/Resources/dots/data_pointy.png new file mode 100644 index 0000000..0dc20f3 Binary files /dev/null and b/MyQrCode/Resources/dots/data_pointy.png differ diff --git a/MyQrCode/Resources/dots/data_roundedRect.png b/MyQrCode/Resources/dots/data_roundedRect.png new file mode 100644 index 0000000..89b0758 Binary files /dev/null and b/MyQrCode/Resources/dots/data_roundedRect.png differ diff --git a/MyQrCode/Resources/dots/data_shiny.png b/MyQrCode/Resources/dots/data_shiny.png new file mode 100644 index 0000000..d61db5c Binary files /dev/null and b/MyQrCode/Resources/dots/data_shiny.png differ diff --git a/MyQrCode/Resources/dots/data_square.png b/MyQrCode/Resources/dots/data_square.png new file mode 100644 index 0000000..ca6aa24 Binary files /dev/null and b/MyQrCode/Resources/dots/data_square.png differ diff --git a/MyQrCode/Resources/dots/data_vertical.png b/MyQrCode/Resources/dots/data_vertical.png new file mode 100644 index 0000000..d31d5da Binary files /dev/null and b/MyQrCode/Resources/dots/data_vertical.png differ diff --git a/MyQrCode/Resources/dots/data_vortex.png b/MyQrCode/Resources/dots/data_vortex.png new file mode 100644 index 0000000..1a1d7ee Binary files /dev/null and b/MyQrCode/Resources/dots/data_vortex.png differ diff --git a/MyQrCode/Resources/dots/data_wex.png b/MyQrCode/Resources/dots/data_wex.png new file mode 100644 index 0000000..e0609c3 Binary files /dev/null and b/MyQrCode/Resources/dots/data_wex.png differ diff --git a/MyQrCode/Resources/eyes/eye_arc.png b/MyQrCode/Resources/eyes/eye_arc.png new file mode 100644 index 0000000..9b6ac5f Binary files /dev/null and b/MyQrCode/Resources/eyes/eye_arc.png differ diff --git a/MyQrCode/Resources/eyes/eye_barsHorizontal.png b/MyQrCode/Resources/eyes/eye_barsHorizontal.png new file mode 100644 index 0000000..2ceca5e Binary files /dev/null and b/MyQrCode/Resources/eyes/eye_barsHorizontal.png differ diff --git a/MyQrCode/Resources/eyes/eye_circle.png b/MyQrCode/Resources/eyes/eye_circle.png new file mode 100644 index 0000000..d173307 Binary files /dev/null and b/MyQrCode/Resources/eyes/eye_circle.png differ diff --git a/MyQrCode/Resources/eyes/eye_cloud.png b/MyQrCode/Resources/eyes/eye_cloud.png new file mode 100644 index 0000000..dc45501 Binary files /dev/null and b/MyQrCode/Resources/eyes/eye_cloud.png differ diff --git a/MyQrCode/Resources/eyes/eye_cloudCircle.png b/MyQrCode/Resources/eyes/eye_cloudCircle.png new file mode 100644 index 0000000..c47f96e Binary files /dev/null and b/MyQrCode/Resources/eyes/eye_cloudCircle.png differ diff --git a/MyQrCode/Resources/eyes/eye_corneredPixels.png b/MyQrCode/Resources/eyes/eye_corneredPixels.png new file mode 100644 index 0000000..a1aae49 Binary files /dev/null and b/MyQrCode/Resources/eyes/eye_corneredPixels.png differ diff --git a/MyQrCode/Resources/eyes/eye_explode.png b/MyQrCode/Resources/eyes/eye_explode.png new file mode 100644 index 0000000..8cd21cd Binary files /dev/null and b/MyQrCode/Resources/eyes/eye_explode.png differ diff --git a/MyQrCode/Resources/eyes/eye_eye.png b/MyQrCode/Resources/eyes/eye_eye.png new file mode 100644 index 0000000..a12c3c1 Binary files /dev/null and b/MyQrCode/Resources/eyes/eye_eye.png differ diff --git a/MyQrCode/Resources/eyes/eye_fireball.png b/MyQrCode/Resources/eyes/eye_fireball.png new file mode 100644 index 0000000..2e3e185 Binary files /dev/null and b/MyQrCode/Resources/eyes/eye_fireball.png differ diff --git a/MyQrCode/Resources/eyes/eye_headlight.png b/MyQrCode/Resources/eyes/eye_headlight.png new file mode 100644 index 0000000..9b24ac8 Binary files /dev/null and b/MyQrCode/Resources/eyes/eye_headlight.png differ diff --git a/MyQrCode/Resources/eyes/eye_leaf.png b/MyQrCode/Resources/eyes/eye_leaf.png new file mode 100644 index 0000000..019535a Binary files /dev/null and b/MyQrCode/Resources/eyes/eye_leaf.png differ diff --git a/MyQrCode/Resources/eyes/eye_pinch.png b/MyQrCode/Resources/eyes/eye_pinch.png new file mode 100644 index 0000000..5c4f7b1 Binary files /dev/null and b/MyQrCode/Resources/eyes/eye_pinch.png differ diff --git a/MyQrCode/Resources/eyes/eye_roundedOuter.png b/MyQrCode/Resources/eyes/eye_roundedOuter.png new file mode 100644 index 0000000..b071fb2 Binary files /dev/null and b/MyQrCode/Resources/eyes/eye_roundedOuter.png differ diff --git a/MyQrCode/Resources/eyes/eye_shield.png b/MyQrCode/Resources/eyes/eye_shield.png new file mode 100644 index 0000000..2cfd051 Binary files /dev/null and b/MyQrCode/Resources/eyes/eye_shield.png differ diff --git a/MyQrCode/Resources/eyes/eye_square.png b/MyQrCode/Resources/eyes/eye_square.png new file mode 100644 index 0000000..09c39bc Binary files /dev/null and b/MyQrCode/Resources/eyes/eye_square.png differ diff --git a/MyQrCode/Resources/eyes/eye_squarePeg.png b/MyQrCode/Resources/eyes/eye_squarePeg.png new file mode 100644 index 0000000..fc0a123 Binary files /dev/null and b/MyQrCode/Resources/eyes/eye_squarePeg.png differ diff --git a/MyQrCode/Resources/eyes/eye_usePixelShape.png b/MyQrCode/Resources/eyes/eye_usePixelShape.png new file mode 100644 index 0000000..4ba91fb Binary files /dev/null and b/MyQrCode/Resources/eyes/eye_usePixelShape.png differ diff --git a/MyQrCode/Views/CreateQRCodeView.swift b/MyQrCode/Views/CreateQRCodeView.swift index ba507a5..2addfb4 100644 --- a/MyQrCode/Views/CreateQRCodeView.swift +++ b/MyQrCode/Views/CreateQRCodeView.swift @@ -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 diff --git a/MyQrCode/Views/QRCodeStyleView.swift b/MyQrCode/Views/QRCodeStyleView.swift new file mode 100644 index 0000000..0c1b8d6 --- /dev/null +++ b/MyQrCode/Views/QRCodeStyleView.swift @@ -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 + ) -> 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") +} diff --git a/docs/CREATE_QRCODE_TO_STYLE_VIEW_README.md b/docs/CREATE_QRCODE_TO_STYLE_VIEW_README.md new file mode 100644 index 0000000..896fad7 --- /dev/null +++ b/docs/CREATE_QRCODE_TO_STYLE_VIEW_README.md @@ -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. 保存到相册和历史记录 + +这个实现提供了更好的用户体验,让用户能够创建个性化的二维码,同时保持了原有的功能完整性。 diff --git a/docs/QRCODE_STYLE_VIEW_README.md b/docs/QRCODE_STYLE_VIEW_README.md new file mode 100644 index 0000000..7269424 --- /dev/null +++ b/docs/QRCODE_STYLE_VIEW_README.md @@ -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. **动画效果**: 添加选择时的动画效果