|
|
|
@ -1,6 +1,7 @@
|
|
|
|
|
import SwiftUI
|
|
|
|
|
import AVFoundation
|
|
|
|
|
internal import Combine
|
|
|
|
|
import UIKit
|
|
|
|
|
import Combine
|
|
|
|
|
|
|
|
|
|
// 通知名称扩展
|
|
|
|
|
extension Notification.Name {
|
|
|
|
@ -21,6 +22,7 @@ struct ScannerView: View {
|
|
|
|
|
@State private var showPreviewPause = false
|
|
|
|
|
@State private var previewLayer: AVCaptureVideoPreviewLayer?
|
|
|
|
|
@State private var screenOrientation = UIDevice.current.orientation
|
|
|
|
|
@State private var selectedScanningStyle: ScanningLineStyle = .modern
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
|
ZStack {
|
|
|
|
@ -32,33 +34,12 @@ struct ScannerView: View {
|
|
|
|
|
VStack {
|
|
|
|
|
Spacer()
|
|
|
|
|
|
|
|
|
|
// 扫描框
|
|
|
|
|
ZStack {
|
|
|
|
|
RoundedRectangle(cornerRadius: 20)
|
|
|
|
|
.stroke(Color.white, lineWidth: 3)
|
|
|
|
|
.frame(width: 250, height: 250)
|
|
|
|
|
.background(Color.black.opacity(0.3))
|
|
|
|
|
.cornerRadius(20)
|
|
|
|
|
|
|
|
|
|
// 扫描线动画
|
|
|
|
|
if !showPreviewPause {
|
|
|
|
|
Rectangle()
|
|
|
|
|
.fill(LinearGradient(
|
|
|
|
|
colors: [Color.clear, Color.green, Color.clear],
|
|
|
|
|
startPoint: .top,
|
|
|
|
|
endPoint: .bottom
|
|
|
|
|
))
|
|
|
|
|
.frame(width: 250, height: 2)
|
|
|
|
|
.offset(y: -125)
|
|
|
|
|
.animation(
|
|
|
|
|
Animation.linear(duration: 2)
|
|
|
|
|
.repeatForever(autoreverses: false),
|
|
|
|
|
value: UUID()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
// 扫描线组件
|
|
|
|
|
if !showPreviewPause {
|
|
|
|
|
ScanningLineView(style: selectedScanningStyle)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 提示文本
|
|
|
|
|
// 提示文本
|
|
|
|
|
if showPreviewPause {
|
|
|
|
|
VStack(spacing: 8) {
|
|
|
|
|
Text("检测到条码")
|
|
|
|
@ -78,15 +59,33 @@ struct ScannerView: View {
|
|
|
|
|
.padding(.top, 20)
|
|
|
|
|
} else {
|
|
|
|
|
Text("将二维码或条形码放入框内")
|
|
|
|
|
.foregroundColor(.white)
|
|
|
|
|
.font(.headline)
|
|
|
|
|
.padding(.top, 20)
|
|
|
|
|
.foregroundColor(.white)
|
|
|
|
|
.font(.headline)
|
|
|
|
|
.padding(.top, 20)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Spacer()
|
|
|
|
|
|
|
|
|
|
// 底部按钮区域
|
|
|
|
|
VStack(spacing: 15) {
|
|
|
|
|
// 扫描线样式选择器
|
|
|
|
|
if !showPreviewPause {
|
|
|
|
|
HStack(spacing: 10) {
|
|
|
|
|
ForEach(ScanningLineStyle.allCases, id: \.self) { style in
|
|
|
|
|
Button(style.rawValue) {
|
|
|
|
|
selectedScanningStyle = style
|
|
|
|
|
}
|
|
|
|
|
.foregroundColor(.white)
|
|
|
|
|
.padding(.horizontal, 8)
|
|
|
|
|
.padding(.vertical, 4)
|
|
|
|
|
.background(selectedScanningStyle == style ? Color.green : Color.gray.opacity(0.6))
|
|
|
|
|
.cornerRadius(8)
|
|
|
|
|
.font(.caption)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.padding(.bottom, 10)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if showPreviewPause {
|
|
|
|
|
// 预览暂停时的按钮
|
|
|
|
|
Button("重新扫描") {
|
|
|
|
@ -143,8 +142,6 @@ struct ScannerView: View {
|
|
|
|
|
Spacer()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
.onAppear {
|
|
|
|
|
scannerViewModel.startScanning()
|
|
|
|
@ -160,6 +157,7 @@ struct ScannerView: View {
|
|
|
|
|
Text("您的设备不支持扫描二维码。请使用带相机的设备。")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.onReceive(scannerViewModel.$detectedCodes) { codes in
|
|
|
|
|
if !codes.isEmpty {
|
|
|
|
|
print("检测到条码数量: \(codes.count)")
|
|
|
|
@ -260,6 +258,8 @@ class ScannerViewModel: NSObject, ObservableObject, AVCaptureMetadataOutputObjec
|
|
|
|
|
setupCaptureSession()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - 相机设置
|
|
|
|
|
|
|
|
|
|
private func setupCaptureSession() {
|
|
|
|
|
captureSession = AVCaptureSession()
|
|
|
|
|
|
|
|
|
@ -471,7 +471,216 @@ struct CodePositionMarker: View {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 扫描线动画修饰符
|
|
|
|
|
struct ScanningLineModifier: ViewModifier {
|
|
|
|
|
@State private var isAnimating = false
|
|
|
|
|
|
|
|
|
|
func body(content: Content) -> some View {
|
|
|
|
|
content
|
|
|
|
|
.offset(y: isAnimating ? 150 : -150)
|
|
|
|
|
.onAppear {
|
|
|
|
|
withAnimation(
|
|
|
|
|
Animation.linear(duration: 2)
|
|
|
|
|
.repeatForever(autoreverses: false)
|
|
|
|
|
) {
|
|
|
|
|
isAnimating = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 脉冲动画修饰符
|
|
|
|
|
struct PulseAnimationModifier: ViewModifier {
|
|
|
|
|
@State private var isPulsing = false
|
|
|
|
|
|
|
|
|
|
func body(content: Content) -> some View {
|
|
|
|
|
content
|
|
|
|
|
.scaleEffect(isPulsing ? 1.5 : 1.0)
|
|
|
|
|
.opacity(isPulsing ? 0.0 : 0.8)
|
|
|
|
|
.onAppear {
|
|
|
|
|
withAnimation(
|
|
|
|
|
Animation.easeInOut(duration: 1.5)
|
|
|
|
|
.repeatForever(autoreverses: false)
|
|
|
|
|
) {
|
|
|
|
|
isPulsing = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 扫描线样式枚举
|
|
|
|
|
enum ScanningLineStyle: String, CaseIterable {
|
|
|
|
|
case modern = "现代科技"
|
|
|
|
|
case classic = "经典简约"
|
|
|
|
|
case neon = "霓虹炫酷"
|
|
|
|
|
case minimal = "极简主义"
|
|
|
|
|
case retro = "复古风格"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#Preview {
|
|
|
|
|
ScannerView()
|
|
|
|
|
}
|
|
|
|
|
// 扫描线组件
|
|
|
|
|
struct ScanningLineView: View {
|
|
|
|
|
let style: ScanningLineStyle
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
|
switch style {
|
|
|
|
|
case .modern:
|
|
|
|
|
ModernScanningLine()
|
|
|
|
|
case .classic:
|
|
|
|
|
ClassicScanningLine()
|
|
|
|
|
case .neon:
|
|
|
|
|
NeonScanningLine()
|
|
|
|
|
case .minimal:
|
|
|
|
|
MinimalScanningLine()
|
|
|
|
|
case .retro:
|
|
|
|
|
RetroScanningLine()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 现代科技风格扫描线
|
|
|
|
|
struct ModernScanningLine: View {
|
|
|
|
|
var body: some View {
|
|
|
|
|
ZStack {
|
|
|
|
|
// 外发光效果
|
|
|
|
|
Rectangle()
|
|
|
|
|
.fill(LinearGradient(
|
|
|
|
|
colors: [Color.clear, Color.green.opacity(0.3), Color.clear],
|
|
|
|
|
startPoint: .top,
|
|
|
|
|
endPoint: .bottom
|
|
|
|
|
))
|
|
|
|
|
.frame(width: 320, height: 8)
|
|
|
|
|
.blur(radius: 3)
|
|
|
|
|
|
|
|
|
|
// 主扫描线
|
|
|
|
|
Rectangle()
|
|
|
|
|
.fill(LinearGradient(
|
|
|
|
|
colors: [Color.clear, Color.green, Color.clear],
|
|
|
|
|
startPoint: .top,
|
|
|
|
|
endPoint: .bottom
|
|
|
|
|
))
|
|
|
|
|
.frame(width: 300, height: 3)
|
|
|
|
|
.shadow(color: .green, radius: 2, x: 0, y: 0)
|
|
|
|
|
|
|
|
|
|
// 内发光效果
|
|
|
|
|
Rectangle()
|
|
|
|
|
.fill(LinearGradient(
|
|
|
|
|
colors: [Color.clear, Color.green.opacity(0.8), Color.clear],
|
|
|
|
|
startPoint: .top,
|
|
|
|
|
endPoint: .bottom
|
|
|
|
|
))
|
|
|
|
|
.frame(width: 300, height: 1)
|
|
|
|
|
.blur(radius: 1)
|
|
|
|
|
|
|
|
|
|
// 中心扫描点
|
|
|
|
|
Circle()
|
|
|
|
|
.fill(Color.green)
|
|
|
|
|
.frame(width: 8, height: 8)
|
|
|
|
|
.shadow(color: .green, radius: 4, x: 0, y: 0)
|
|
|
|
|
|
|
|
|
|
// 扫描点外圈
|
|
|
|
|
Circle()
|
|
|
|
|
.stroke(Color.green.opacity(0.6), lineWidth: 2)
|
|
|
|
|
.frame(width: 16, height: 16)
|
|
|
|
|
|
|
|
|
|
// 脉冲效果
|
|
|
|
|
Circle()
|
|
|
|
|
.stroke(Color.green.opacity(0.4), lineWidth: 1)
|
|
|
|
|
.frame(width: 24, height: 24)
|
|
|
|
|
.scaleEffect(1.0)
|
|
|
|
|
.opacity(0.8)
|
|
|
|
|
.modifier(PulseAnimationModifier())
|
|
|
|
|
}
|
|
|
|
|
.modifier(ScanningLineModifier())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 经典简约风格扫描线
|
|
|
|
|
struct ClassicScanningLine: View {
|
|
|
|
|
var body: some View {
|
|
|
|
|
Rectangle()
|
|
|
|
|
.fill(Color.green)
|
|
|
|
|
.frame(width: 300, height: 2)
|
|
|
|
|
.modifier(ScanningLineModifier())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 霓虹炫酷风格扫描线
|
|
|
|
|
struct NeonScanningLine: View {
|
|
|
|
|
var body: some View {
|
|
|
|
|
ZStack {
|
|
|
|
|
// 霓虹外发光
|
|
|
|
|
Rectangle()
|
|
|
|
|
.fill(LinearGradient(
|
|
|
|
|
colors: [Color.clear, Color.cyan.opacity(0.6), Color.clear],
|
|
|
|
|
startPoint: .top,
|
|
|
|
|
endPoint: .bottom
|
|
|
|
|
))
|
|
|
|
|
.frame(width: 340, height: 12)
|
|
|
|
|
.blur(radius: 4)
|
|
|
|
|
|
|
|
|
|
// 主扫描线
|
|
|
|
|
Rectangle()
|
|
|
|
|
.fill(LinearGradient(
|
|
|
|
|
colors: [Color.cyan, Color.blue, Color.cyan],
|
|
|
|
|
startPoint: .leading,
|
|
|
|
|
endPoint: .trailing
|
|
|
|
|
))
|
|
|
|
|
.frame(width: 300, height: 4)
|
|
|
|
|
.shadow(color: .cyan, radius: 6, x: 0, y: 0)
|
|
|
|
|
|
|
|
|
|
// 中心亮点
|
|
|
|
|
Circle()
|
|
|
|
|
.fill(Color.white)
|
|
|
|
|
.frame(width: 6, height: 6)
|
|
|
|
|
.shadow(color: .white, radius: 8, x: 0, y: 0)
|
|
|
|
|
}
|
|
|
|
|
.modifier(ScanningLineModifier())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 极简主义风格扫描线
|
|
|
|
|
struct MinimalScanningLine: View {
|
|
|
|
|
var body: some View {
|
|
|
|
|
Rectangle()
|
|
|
|
|
.fill(Color.white.opacity(0.8))
|
|
|
|
|
.frame(width: 280, height: 1)
|
|
|
|
|
.modifier(ScanningLineModifier())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 复古风格扫描线
|
|
|
|
|
struct RetroScanningLine: View {
|
|
|
|
|
var body: some View {
|
|
|
|
|
ZStack {
|
|
|
|
|
// 复古扫描线
|
|
|
|
|
Rectangle()
|
|
|
|
|
.fill(LinearGradient(
|
|
|
|
|
colors: [Color.clear, Color.orange, Color.clear],
|
|
|
|
|
startPoint: .top,
|
|
|
|
|
endPoint: .bottom
|
|
|
|
|
))
|
|
|
|
|
.frame(width: 300, height: 3)
|
|
|
|
|
.overlay(
|
|
|
|
|
Rectangle()
|
|
|
|
|
.stroke(Color.orange, lineWidth: 1)
|
|
|
|
|
.frame(width: 300, height: 3)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 复古扫描点
|
|
|
|
|
Circle()
|
|
|
|
|
.fill(Color.orange)
|
|
|
|
|
.frame(width: 4, height: 4)
|
|
|
|
|
}
|
|
|
|
|
.modifier(ScanningLineModifier())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if DEBUG
|
|
|
|
|
struct ScannerView_Previews: PreviewProvider {
|
|
|
|
|
static var previews: some View {
|
|
|
|
|
ScannerView()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|