我的应用程序遇到异常行为。我有一个视图ToolDetail
(下面的代码),可以选择显示有关(硬件)工具的照片,然后显示一组信息(非可选),最后显示匹配的驱动程序(应要求提供)-请参见第一张图片(带有照片)和第二张图片(没有照片)。我遇到的问题是,即使没有图像,匹配的驱动程序也不会显示,尽管它们已加载并可以作为计算属性使用,就像没有照片的版本一样。
这是import SwiftUI
struct ToolDetail: View {
var tool: Tool
@EnvironmentObject var userData: UserData
@State private var showMatchingDrivers = false
@State private var showMatchingAdapters = false
@State private var showSheet = false
@State private var activeSheet = ActiveDetailSheet.editor
private var matchingDrivers: [Driver] {
//tool.driverCouplingId != nil ? tool.getMatchingDrivers(drivers: userData.getDrivers()) : [Driver]()
var matchingDrivers = [Driver]()
if tool.driverCouplingId != nil { matchingDrivers = tool.getMatchingDrivers(drivers: userData.getDrivers()) }
return matchingDrivers
}
private var matchingAdapters: [Adapter] {
tool.driverCouplingId != nil ? tool.getMatchingAdapters(adapters: userData.getAdapters()) : [Adapter]()
}
var body: some View {
ScrollView {
VStack {
HStack {
Spacer()
Button(action: {
self.activeSheet = .editor
self.showSheet.toggle()
}) {
Text("Edit")
}
}
.padding()
HStack {
Text(tool.name)
.font(.headline)
.contextMenu { Button(action: { UIPasteboard.general.string = self.tool.name }) { Text("Copy") } }
if tool.isFavorite {
Image(systemName: "star.fill")
.imageScale(.medium)
.foregroundColor(.yellow)
}
}
// Photo (optional)
if tool.photoRecordReference != nil {
if userData.instrumentPhotos.photoHasBeenLoaded(recordId: tool.photoRecordReference!.recordID) {
VStack {
Image(uiImage: userData.instrumentPhotos.getPhoto(recordId: tool.photoRecordReference!.recordID)!.photo)
.resizable()
.aspectRatio(userData.instrumentPhotos.getPhoto(recordId: tool.photoRecordReference!.recordID)!.photo.size, contentMode: .fit)
.padding()
.onTapGesture {
self.activeSheet = .photoViewer
self.showSheet = true
}
Text(userData.instrumentPhotos.getPhoto(recordId: tool.photoRecordReference!.recordID)!.caption)
}
}
}
// Category
HStack(alignment: .top) {
Text("Category")
.fontWeight(.bold)
Spacer()
Text(tool.category)
.contextMenu { Button(action: { UIPasteboard.general.string = self.tool.category }) { Text("Copy") } }
}
.padding()
HStack(alignment: .top) {
// Tool Profile
Text("Profile")
.fontWeight(.bold)
Spacer()
Text(tool.profileType)
.contextMenu { Button(action: { UIPasteboard.general.string = self.tool.profileType }) { Text("Copy") } }
Text(tool.profileSize)
.contextMenu { Button(action: { UIPasteboard.general.string = self.tool.profileSize }) { Text("Copy") } }
}
.padding()
// Details
Group {
// Length (optional)
if tool.length != nil {
HStack {
Text("Length")
.fontWeight(.bold)
Spacer()
Text(tool.length!)
.contextMenu { Button(action: { UIPasteboard.general.string = self.tool.length }) { Text("Copy") } }
}
.padding()
}
// Driver Coupling (optional)
if tool.driverCouplingId != nil {
HStack {
Text("Driver Coupling")
.fontWeight(.bold)
Spacer()
Text(NSLocalizedString(DataHelper.couplingData[tool.driverCouplingId!]!.size.rawValue, comment: ""))
Text(NSLocalizedString(DataHelper.couplingData[tool.driverCouplingId!]!.driverGender.rawValue, comment: ""))
Text(NSLocalizedString(DataHelper.couplingData[tool.driverCouplingId!]!.profile.rawValue, comment: ""))
}
.padding()
}
// Set (optional)
if tool.set != nil {
HStack {
Text("Set")
.fontWeight(.bold)
Spacer()
Text(tool.set!)
.contextMenu { Button(action: { UIPasteboard.general.string = self.tool.set }) { Text("Copy") } }
}
.padding()
}
// Location (optional)
if tool.location != nil {
HStack {
Text("Location")
.fontWeight(.bold)
Spacer()
Text(tool.location!)
.contextMenu { Button(action: { UIPasteboard.general.string = self.tool.location }) { Text("Copy") } }
}
.padding()
}
// Comment (optional)
if tool.comment != nil {
HStack {
Text("Comment")
.fontWeight(.bold)
Spacer()
Text(tool.comment!)
.contextMenu { Button(action: { UIPasteboard.general.string = self.tool.comment }) { Text("Copy") } }
}
.padding()
}
}
//
// Matching Drivers
//
if (tool.driverCouplingId != nil) { // The tool needs a driver or can be plugged into an adapter
HStack {
Text("Matching Drivers")
.font(.headline)
.padding()
Spacer()
Button(action: {
withAnimation {
self.showMatchingDrivers.toggle()
}
}) {
Image(systemName: "chevron.right.circle")
.imageScale(.large)
.rotationEffect(.degrees(showMatchingDrivers ? 90 : 0))
.scaleEffect(showMatchingDrivers ? 1.5 : 1)
.padding()
}
}
if showMatchingDrivers {
List {
ForEach(matchingDrivers) { driver in
DriverRow(driver: driver)
}
}
.transition(.moveAndFade)
}
//
// Matching Adapters
//
HStack {
Text("Matching Adapters")
.font(.headline)
.padding()
Spacer()
Button(action: {
withAnimation {
self.showMatchingAdapters.toggle()
}
}) {
Image(systemName: "chevron.right.circle")
.imageScale(.large)
.rotationEffect(.degrees(showMatchingAdapters ? 90 : 0))
.scaleEffect(showMatchingAdapters ? 1.5 : 1)
.padding()
}
}
if showMatchingAdapters {
List {
ForEach(matchingAdapters) { adapter in
AdapterRow(adapter: adapter)
}
}
.transition(.moveAndFade)
}
}
}
}
.navigationBarTitle(Text(tool.name), displayMode: .automatic)
.sheet(isPresented: $showSheet) {
DetailSheetViews(showingSheet: self.$showSheet, activeSheet: self.activeSheet, instrumentType: InstrumentType.tool, draftInstrument: self.tool)
.environmentObject(self.userData)
}
.onAppear() {
if self.tool.photoRecordReference != nil {
self.userData.loadPhotoFromCK(photoRecordId: self.tool.photoRecordReference!.recordID)
}
}
}
}
的代码:
Driver
行为:在调试没有照片的工具时,我正确地获得了一个matchingDrivers
数组,该数组在计算的属性if showMatchingDrivers {
List {
ForEach(matchingDrivers) { driver in
DriverRow(driver: driver)
}
}
.transition(.moveAndFade)
}
中具有2个值。稍后在代码中
DriverRow
ForEach
应该在DriverRow
循环中显示。在这两种情况下,无论有无照片,都将调用matchingDriver
struct-但是在有照片的版本中正确调用两次(并显示),在没有照片的版本中仅调用一次,尽管{中加载了两个驱动程序{1}}属性,但没有显示。
这是DriverRow
代码:
import SwiftUI
struct DriverRow: View {
var driver: Driver
@EnvironmentObject var userData: UserData
var body: some View {
HStack {
if driver.photoRecordReference != nil && userData.instrumentPhotos.photoHasBeenLoaded(recordId: driver.photoRecordReference!.recordID) {
HStack {
Image(uiImage: userData.instrumentPhotos.getPhoto(recordId: driver.photoRecordReference!.recordID)!.photo)
.resizable()
.scaledToFit()
.frame(width: 50)
}
}
VStack(alignment: .leading) {
Text(driver.name)
.font(.headline)
.contextMenu { Button(action: { UIPasteboard.general.string = self.driver.name }) { Text("Copy") } }
Text(driver.category)
.font(.caption)
HStack {
Text(NSLocalizedString(DataHelper.couplingData[driver.couplingId]!.size.rawValue, comment: ""))
Text(NSLocalizedString(DataHelper.couplingData[driver.couplingId]!.driverGender.rawValue, comment: ""))
Text(NSLocalizedString(DataHelper.couplingData[driver.couplingId]!.profile.rawValue, comment: ""))
Spacer()
}
}
if driver.isFavorite {
Image(systemName: "star.fill")
.imageScale(.medium)
.foregroundColor(.yellow)
}
}
.onAppear() {
if self.driver.photoRecordReference != nil && !self.userData.instrumentPhotos.photoHasBeenLoaded(recordId: self.driver.photoRecordReference!.recordID) {
self.userData.loadPhotoFromCK(photoRecordId: self.driver.photoRecordReference!.recordID)
}
}
}
}
工具,驱动程序以及照片通过CloudKit存储在iCloud上。加载工作是“延迟的”,即仅在需要时才加载数据。所有这些都存储在UserData
类中(下面是缩短的版本)。所有加载工作正常,我不认为这是造成这种情况的原因,但是,我仍然发布了相关代码:
import SwiftUI
import CloudKit
final class UserData: ObservableObject {
// States of modals and general settings
@Published var showFavoritesOnly = false
@ObservedObject var toolVM = ToolViewModel()
@ObservedObject var driverVM = DriverViewModel()
@ObservedObject var adapterVM = AdapterViewModel()
var instrumentPhotos = InstrumentPhotos()
func getTools() -> [Tool] {
if !toolVM.instrumentsHaveBeenLoaded {
loadTools()
}
return toolVM.tools
}
func getDrivers() -> [Driver] {
if !driverVM.instrumentsHaveBeenLoaded {
loadDrivers()
}
return driverVM.drivers
}
func getAdapters() -> [Adapter] {
if !adapterVM.instrumentsHaveBeenLoaded {
loadAdapters()
}
return adapterVM.adapters
}
private func loadTools() {
DataHelper.loadFromCK(instrumentType: .tools) { (result) in
switch result {
case .success(let loadedInstruments):
self.toolVM.tools = loadedInstruments as! [Tool]
self.toolVM.setInstrumentCategories(instruments: loadedInstruments)
self.toolVM.instrumentsHaveBeenLoaded = true
self.toolVM.objectWillChange.send()
self.objectWillChange.send()
debugPrint("Successfully loaded instruments of type Tools and initialized categories and category dictionary")
case .failure(let error):
debugPrint(error.localizedDescription)
}
}
}
private func loadDrivers() {
DataHelper.loadFromCK(instrumentType: .drivers) { (result) in
switch result {
case .success(let loadedInstruments):
self.driverVM.drivers = loadedInstruments as! [Driver]
self.driverVM.setInstrumentCategories(instruments: loadedInstruments)
self.driverVM.instrumentsHaveBeenLoaded = true
self.driverVM.objectWillChange.send()
self.objectWillChange.send()
debugPrint("Successfully loaded instruments of type Drivers and initialized categories and category dictionary")
case .failure(let error):
debugPrint(error.localizedDescription)
}
}
}
private func loadAdapters() {
DataHelper.loadFromCK(instrumentType: .adapters) { (result) in
switch result {
case .success(let loadedInstruments):
self.adapterVM.adapters = loadedInstruments as! [Adapter]
self.adapterVM.setInstrumentCategories(instruments: loadedInstruments)
self.adapterVM.instrumentsHaveBeenLoaded = true
self.adapterVM.objectWillChange.send()
self.objectWillChange.send()
debugPrint("Successfully loaded instruments of type Adapters and initialized categories and category dictionary")
case .failure(let error):
debugPrint(error.localizedDescription)
}
}
}
func loadPhotoFromCK(photoRecordId: CKRecord.ID) {
if !self.instrumentPhotos.photoHasBeenLoaded(recordId: photoRecordId) {
DataHelper.loadPhotoFromCK(photoRecordId: photoRecordId) { result in
switch result {
case .success(let instrumentPhoto):
self.instrumentPhotos.addPhoto(instrumentPhoto: instrumentPhoto!)
self.objectWillChange.send()
debugPrint("Successfully loaded photo \(photoRecordId) '\(instrumentPhoto!.caption)' from iCloud")
case .failure(let err):
debugPrint(err.localizedDescription)
}
}
}
}
func loadPhotosFromCK() {
if !self.instrumentPhotos.allPhotosLoaded {
DataHelper.loadPhotosFromCK() { result in
switch result {
case .success(let instrumentPhotos):
self.instrumentPhotos.photos = instrumentPhotos
self.instrumentPhotos.allPhotosLoaded = true
self.objectWillChange.send()
debugPrint("Successfully loaded all photos from iCloud")
case .failure(let err):
debugPrint(err.localizedDescription)
}
}
}
}
func checkForPhoto(photoRecordId: CKRecord.ID, instrumentName: inout String) -> Bool {
// Check tools
for tool in self.toolVM.tools {
if tool.checkForPhoto(photoRecordId: photoRecordId) {
instrumentName = tool.name
return true
}
}
// Check drivers
for driver in self.driverVM.drivers {
if driver.checkForPhoto(photoRecordId: photoRecordId) {
instrumentName = driver.name
return true
}
}
// Check adapters
for adapter in self.adapterVM.adapters {
if adapter.checkForPhoto(photoRecordId: photoRecordId) {
instrumentName = adapter.name
return true
}
}
return false
}
}
DataHelper
代码的最后但并非最不重要的部分:
import SwiftUI
import CloudKit
struct DataHelper {
static func loadFromCK(instrumentType: InstrumentCKDataTypes, completion: @escaping (Result<[Instrument], Error>) -> ()) {
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: instrumentType.rawValue, predicate: predicate)
getCKRecords(instrumentType: instrumentType, forQuery: query, completion: completion)
}
private static func getCKRecords(instrumentType: InstrumentCKDataTypes, forQuery query: CKQuery, completion: @escaping (Result<[Instrument], Error>) -> ()) {
CKContainer.default().publicCloudDatabase.perform(query, inZoneWith: CKRecordZone.default().zoneID) { results, error in
if let error = error {
DispatchQueue.main.async { completion(.failure(error)) }
return
}
guard let results = results else { return }
switch instrumentType {
case .tools:
DispatchQueue.main.async { completion(.success(results.compactMap { Tool(record: $0) })) }
case .drivers:
DispatchQueue.main.async { completion(.success(results.compactMap { Driver(record: $0) })) }
case .adapters:
DispatchQueue.main.async { completion(.success(results.compactMap { Adapter(record: $0) })) }
}
}
}
static func loadPhotoFromCK(photoRecordId: CKRecord.ID, completion: @escaping (Result<InstrumentPhoto?, Error>) -> ()) {
CKContainer.default().publicCloudDatabase.fetch(withRecordID: photoRecordId) { record, err in
if let err = err {
DispatchQueue.main.async { completion(.failure(err)) }
return
}
guard let photoRecord = record else {
DispatchQueue.main.async { completion(.failure(CloudKitError.recordFailure)) }
return
}
DispatchQueue.global(qos: .utility).async {
var image: InstrumentPhoto?
defer {
DispatchQueue.main.async {
completion(.success(image))
}
}
guard
let photoAsset = photoRecord["photo"] as? CKAsset,
let caption = photoRecord["caption"] as? String
else { return }
guard let uiImage = makeUIImage(from: photoAsset) else { return }
image = InstrumentPhoto(photo: uiImage, caption: caption, recordId: photoRecordId)
}
}
}
static func makeUIImage(from photoAsset: CKAsset) -> UIImage? {
guard let fileURL = photoAsset.fileURL else {
debugPrint("Error getting fileURL")
return nil
}
let imageData: Data
do {
imageData = try Data(contentsOf: fileURL)
} catch {
debugPrint("Error getting imageData")
return nil
}
return UIImage(data: imageData)!
}
static func loadPhotosFromCK(completion: @escaping (Result<[InstrumentPhoto], Error>) -> ()) {
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Photos", predicate: predicate)
CKContainer.default().publicCloudDatabase.perform(query, inZoneWith: CKRecordZone.default().zoneID) { results, error in
if let error = error {
DispatchQueue.main.async { completion(.failure(error)) }
return
}
guard let results = results else { return }
DispatchQueue.main.async { completion(.success(results.compactMap { InstrumentPhoto(record: $0) })) }
}
}
}
与matchingAdapters
的行为相同...
我已经搜索了几天,但找不到错误。希望有人有一个主意。谢谢!