为 TabBar 和扫码页补充 accessibilityIdentifier,并在模拟器/UI Test 中禁用真实相机采集。

预初始化各 Tab 路由容器,修正 iOS 17 ContentUnavailableView 调用,提升自定义 TabBar 与扫码流程的 UI Test 稳定性。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-29 09:34:09 +08:00
parent d27438b264
commit 79e735f628
7 changed files with 105 additions and 28 deletions

View File

@ -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