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.

164 lines
6.8 KiB

import SwiftUI
import AVFoundation
// MARK: -
struct CodePositionOverlay: View {
let detectedCodes: [DetectedCode]
let previewLayer: AVCaptureVideoPreviewLayer?
let onCodeSelected: (DetectedCode) -> Void
var body: some View {
ZStack {
GeometryReader { geometry in
ForEach(detectedCodes) { code in
CodePositionMarker(
code: code,
screenSize: geometry.size,
previewLayer: previewLayer,
onCodeSelected: onCodeSelected
)
}
//
#if DEBUG
ForEach(detectedCodes) { code in
let position = calculateDebugPosition(code: code, screenSize: geometry.size)
Rectangle()
.stroke(Color.red, lineWidth: 1)
.frame(width: 80, height: 80)
.position(x: position.x, y: position.y)
.opacity(0.3)
}
#endif
}
.ignoresSafeArea()
}
.allowsHitTesting(true)
.contentShape(Rectangle()) //
.zIndex(1000) //
}
//
private func calculateDebugPosition(code: DetectedCode, screenSize: CGSize) -> CGPoint {
guard let previewLayer = previewLayer else {
return CGPoint(x: screenSize.width / 2, y: screenSize.height / 2)
}
let metadataObject = code.bounds
let convertedPoint = previewLayer.layerPointConverted(fromCaptureDevicePoint: CGPoint(
x: metadataObject.midX,
y: metadataObject.midY
))
let clampedX = max(40, min(screenSize.width - 40, convertedPoint.x))
let clampedY = max(40, min(screenSize.height - 40, convertedPoint.y))
return CGPoint(x: clampedX, y: clampedY)
}
}
// MARK: -
struct CodePositionMarker: View {
let code: DetectedCode
let screenSize: CGSize
let previewLayer: AVCaptureVideoPreviewLayer?
let onCodeSelected: (DetectedCode) -> Void
var body: some View {
GeometryReader { geometry in
let position = calculatePosition(screenSize: geometry.size)
ZStack {
//
Circle()
.fill(Color.clear)
.frame(width: 80, height: 80)
.contentShape(Circle())
.onTapGesture {
logDebug("🎯 CodePositionMarker 被点击!", className: "CodePositionMarker")
logDebug(" 条码ID: \(code.id)", className: "CodePositionMarker")
logDebug(" 条码类型: \(code.type)", className: "CodePositionMarker")
logDebug(" 条码内容: \(code.content)", className: "CodePositionMarker")
logDebug(" 点击位置: x=\(position.x), y=\(position.y)", className: "CodePositionMarker")
//
let impactFeedback = UIImpactFeedbackGenerator(style: .medium)
impactFeedback.impactOccurred()
onCodeSelected(code)
}
//
Circle()
.stroke(Color.green, lineWidth: 3)
.frame(width: 40, height: 40)
//
Circle()
.fill(Color.green.opacity(0.3))
.frame(width: 20, height: 20)
//
Circle()
.fill(Color.green)
.frame(width: 6, height: 6)
}
.position(x: position.x, y: position.y)
.zIndex(1001) //
.onAppear {
logDebug("CodePositionMarker appeared at: x=\(position.x), y=\(position.y)", className: "CodePositionMarker")
logDebug("Screen size: \(geometry.size)", className: "CodePositionMarker")
logDebug("Code bounds: \(code.bounds)", className: "CodePositionMarker")
}
}
}
private func calculatePosition(screenSize: CGSize) -> CGPoint {
guard let previewLayer = previewLayer else {
logWarning("No preview layer available, using screen center", className: "CodePositionMarker")
return CGPoint(x: screenSize.width / 2, y: screenSize.height / 2)
}
// 使使
//
let metadataObject = code.bounds
//
guard metadataObject.width > 0 && metadataObject.height > 0 else {
logWarning("Invalid metadata bounds: \(metadataObject), using screen center", className: "CodePositionMarker")
return CGPoint(x: screenSize.width / 2, y: screenSize.height / 2)
}
let convertedPoint = previewLayer.layerPointConverted(fromCaptureDevicePoint: CGPoint(
x: metadataObject.midX,
y: metadataObject.midY
))
guard convertedPoint.x.isFinite && convertedPoint.y.isFinite else {
logWarning("Invalid converted point: \(convertedPoint), using screen center", className: "CodePositionMarker")
return CGPoint(x: screenSize.width / 2, y: screenSize.height / 2)
}
//
let clampedX = max(20, min(screenSize.width - 20, convertedPoint.x))
let clampedY = max(20, min(screenSize.height - 20, convertedPoint.y))
logDebug("AVFoundation bounds: \(code.bounds)", className: "CodePositionMarker")
logDebug("Converted point: \(convertedPoint)", className: "CodePositionMarker")
logDebug("Screen size: \(screenSize)", className: "CodePositionMarker")
logDebug("Clamped: x=\(clampedX), y=\(clampedY)", className: "CodePositionMarker")
return CGPoint(x: clampedX, y: clampedY)
}
}
// MARK: -
struct RescanButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.scaleEffect(configuration.isPressed ? 0.95 : 1.0)
.opacity(configuration.isPressed ? 0.8 : 1.0)
.animation(.easeInOut(duration: 0.1), value: configuration.isPressed)
}
}