我正在尝试创建一个 iOS 应用,当我扫描产品时,会出现有关该产品的信息。当我扫描产品时,我希望使用 SwiftData 存储它并显示在我的“历史记录”页面中。对于某些
我正在尝试创建一个 iOS 应用,当我扫描产品时,有关该产品的信息就会出现。当我扫描产品时,我希望使用 SwiftData 存储它并显示在我的“历史记录”页面中。出于某种原因,无论我尝试什么,都不会在历史记录视图中显示我扫描过的产品。
这是我目前拥有的代码:
这是我用来获取产品信息的函数。它位于我的 AppViewModel.swift 中。
import AVKit
import Foundation
import SwiftUI
import VisionKit
enum ScanType: String {
case barcode, text
}
enum DataScannerAccessStatusType {
case notDetermined
case cameraAccessNotGranted
case cameraNotAvailable
case scannerAvailable
case scannerNotAvailable
}
@MainActor
final class AppViewModel: ObservableObject {
@Environment(\.modelContext) private var modelContext
@Published var dataScannerAccessStatus: DataScannerAccessStatusType = .notDetermined
@Published var recognizedItems: [RecognizedItem] = []
@Published var scanType: ScanType = .barcode
@Published var textContentType: DataScannerViewController.TextContentType?
@Published var recognizesMultipleItems = true
@Published var showNutritionInfo = false
@Published var productInfo: NutritionInfoView?
public func fetchProductInfo(barcode: String) {
let baseURL = "https://world.openfoodfacts.org/api/v0/product/"
guard let url = URL(string: "\(baseURL)\(barcode).json") else {
print("Invalid URL")
return
}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print("Failed to retrieve data: \(error.localizedDescription)")
return
}
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200, let data = data else {
print("Failed to retrieve data. Status code: \((response as? HTTPURLResponse)?.statusCode ?? 0)")
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let status = json["status"] as? Int, status == 1,
let product = json["product"] as? [String: Any] {
let newProduct = Product(
withAdditives: product["with_additives"] as? String ?? "N/A",
name: product["product_name"] as? String ?? "N/A",
brand: product["brands"] as? String ?? "N/A",
quantity: product["quantity"] as? String ?? "N/A",
ingredients: product["ingredients_text"] as? String ?? "N/A",
nutritionScore: product["nutriscore_score"] as? Int ?? -1,
imageURL: product["image_url"] as? String ?? "N/A"
)
DispatchQueue.main.async {
self.productInfo = NutritionInfoView(product: newProduct)
self.showNutritionInfo = true
self.modelContext.insert(newProduct)
do {
try self.modelContext.save()
} catch {
print("Failed to save context: \(error.localizedDescription)")
}
}
} else {
print("Product not found.")
}
} catch {
print("Failed to parse JSON: \(error.localizedDescription)")
}
}
task.resume()
}
// Other code
}
这是我的 CameraView.swift:
import SwiftUI
import VisionKit
struct CameraView: View {
@EnvironmentObject var vm: AppViewModel
var body: some View {
switch vm.dataScannerAccessStatus {
case .scannerAvailable:
mainView
case .cameraNotAvailable:
Text("Your device doesn't have a camera")
case .scannerNotAvailable:
Text("Your device doesn't have support for scanning barcode with this app")
case .cameraAccessNotGranted:
Text("Please provide access to the camera in settings")
case .notDetermined:
Text("Requesting camera access")
}
}
private var mainView: some View {
ZStack {
DataScannerView(
recognizedItems: $vm.recognizedItems,
recognizedDataType: vm.recognizedDataType,
recognizesMultipleItems: vm.recognizesMultipleItems,
onScan: { result in
// Ensure scanType is barcode
if vm.scanType == .barcode {
// Iterate over recognized items
for item in result {
// Check if the item is a barcode
if case let .barcode(barcode) = item {
// Fetch product info with the barcode payload
vm.fetchProductInfo(barcode: barcode.payloadStringValue ?? "")
// Exit the loop after processing the first barcode (if needed)
break
}
}
}
}
)
.background { Color.gray.opacity(0.3) }
.ignoresSafeArea()
.id(vm.dataScannerViewId)
.sheet(isPresented: $vm.showNutritionInfo) {
// TODO: Make scrollable if not already, maybe through a ScrollablePane (idk what it's rally caleed look at barcode app I think it's there)
if let productInfo = vm.productInfo {
productInfo
.presentationDragIndicator(.visible)
}
}
VStack {
Spacer()
bottomContainerView
.background(.clear)
.frame(maxWidth: .infinity)
.frame(height: UIScreen.main.bounds.height * 0.35) // Height of scanning information
.clipped()
}
.edgesIgnoringSafeArea(.bottom)
.onChange(of: vm.scanType) { _ in vm.recognizedItems = [] }
.onChange(of: vm.textContentType) { _ in vm.recognizedItems = [] }
.onChange(of: vm.recognizesMultipleItems) { _ in vm.recognizedItems = [] }
}
}
private var bottomContainerView: some View {
VStack {
Picker("Scan Type", selection: $vm.scanType) {
Text("Barcode").tag(ScanType.barcode)
Text("Text").tag(ScanType.text)
}
.pickerStyle(.segmented)
.padding(.leading, 30)
.padding(.trailing, 30)
.padding(.horizontal)
}
}
}
这是我的 HistoryView.swift
import SwiftUI
import SwiftData
struct HistoryView: View {
@Environment(\.modelContext) private var modelContext
@Query() private var products: [Product]
var body: some View {
NavigationSplitView {
Group {
if !products.isEmpty {
List {
ForEach(products) { product in
NavigationLink {
NutritionInfoView(product: product)
} label: {
Text(product.name)
}
}
.onDelete(perform: deleteItems)
}
} else {
ContentUnavailableView {
Label("You haven't scanned anything yet", systemImage: "barcode.viewfinder")
}
}
}
.navigationTitle("History")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
}
} detail: {
Text("Select a product")
.navigationTitle("Products")
}
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
for index in offsets {
modelContext.delete(products[index])
}
}
}
}
#Preview {
HistoryView()
}
非常感谢所有帮助!