为 TabBar 和扫码页补充 accessibilityIdentifier,并在模拟器/UI Test 中禁用真实相机采集。
预初始化各 Tab 路由容器,修正 iOS 17 ContentUnavailableView 调用,提升自定义 TabBar 与扫码流程的 UI Test 稳定性。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -106,6 +106,7 @@ private struct CustomMainTabBarItem: View {
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel(item.title)
|
||||
.accessibilityIdentifier("main.tab.\(item.tab.rawValue)")
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,6 +129,7 @@ private struct CustomMainTabBarCenterAction: View {
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel("扫码核销")
|
||||
.accessibilityIdentifier("main.scan")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -34,6 +34,23 @@ struct OrderScannerPage: View {
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
content
|
||||
.navigationTitle("扫码核销")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("关闭") {
|
||||
onClose()
|
||||
}
|
||||
.accessibilityIdentifier("scanner.close")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var content: some View {
|
||||
if shouldUseCameraPreview {
|
||||
ZStack(alignment: .bottom) {
|
||||
OrderCodeScannerView { result in
|
||||
switch result {
|
||||
@ -53,18 +70,40 @@ struct OrderScannerPage: View {
|
||||
.background(.black.opacity(0.55), in: Capsule())
|
||||
.padding(.bottom, AppMetrics.Spacing.xxLarge)
|
||||
}
|
||||
.navigationTitle("扫码核销")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("关闭") {
|
||||
onClose()
|
||||
}
|
||||
.accessibilityIdentifier("scanner.close")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ScannerUnavailableContent()
|
||||
}
|
||||
}
|
||||
|
||||
private var shouldUseCameraPreview: Bool {
|
||||
#if targetEnvironment(simulator)
|
||||
return false
|
||||
#else
|
||||
guard !AppUITestLaunchState.isRunningUITests else { return false }
|
||||
return AVCaptureDevice.default(for: .video) != nil
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// 无可用摄像头时展示的扫码占位内容,避免在模拟器和 UI Test 中启动真实采集会话。
|
||||
private struct ScannerUnavailableContent: View {
|
||||
var body: some View {
|
||||
VStack(spacing: AppMetrics.Spacing.small) {
|
||||
Image(systemName: "camera.viewfinder")
|
||||
.font(.system(size: 48, weight: .semibold))
|
||||
.foregroundStyle(.white)
|
||||
|
||||
Text("当前环境不可用相机")
|
||||
.font(.system(size: AppMetrics.FontSize.title3, weight: .semibold))
|
||||
.foregroundStyle(.white)
|
||||
|
||||
Text("请在真机上使用扫码核销")
|
||||
.font(.system(size: AppMetrics.FontSize.subheadline))
|
||||
.foregroundStyle(.white.opacity(0.72))
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(Color.black)
|
||||
}
|
||||
}
|
||||
|
||||
/// AVFoundation 扫码桥接视图,负责把相机识别结果回传给 SwiftUI。
|
||||
@ -229,7 +268,7 @@ final class OrderScannerViewController: UIViewController, AVCaptureMetadataOutpu
|
||||
session.addOutput(metadataOutput)
|
||||
|
||||
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
|
||||
metadataOutput.metadataObjectTypes = [
|
||||
let desiredTypes: [AVMetadataObject.ObjectType] = [
|
||||
.qr,
|
||||
.ean8,
|
||||
.ean13,
|
||||
@ -242,6 +281,12 @@ final class OrderScannerViewController: UIViewController, AVCaptureMetadataOutpu
|
||||
.itf14,
|
||||
.upce
|
||||
]
|
||||
let availableTypes = Set(metadataOutput.availableMetadataObjectTypes)
|
||||
let supportedTypes = desiredTypes.filter { availableTypes.contains($0) }
|
||||
guard !supportedTypes.isEmpty else {
|
||||
throw OrderScannerError.invalidMetadata
|
||||
}
|
||||
metadataOutput.metadataObjectTypes = supportedTypes
|
||||
|
||||
let preview = AVCaptureVideoPreviewLayer(session: session)
|
||||
preview.videoGravity = .resizeAspectFill
|
||||
|
||||
Reference in New Issue
Block a user