SwiftUI子视图不会显示

时间:2020-07-10 07:45:56

标签: swiftui

我的应用程序遇到异常行为。我有一个视图ToolDetail(下面的代码),可以选择显示有关(硬件)工具的照片,然后显示一组信息(非可选),最后显示匹配的驱动程序(应要求提供)-请参见第一张图片(带有照片)和第二张图片(没有照片)。我遇到的问题是,即使没有图像,匹配的驱动程序也不会显示,尽管它们已加载并可以作为计算属性使用,就像没有照片的版本一样。

The ToolDetail view including a photo - matching drivers are correctly displayed The ToolDetail view without a photo - matching drivers are not displayed although loaded and available as computed property

这是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的行为相同...

我已经搜索了几天,但找不到错误。希望有人有一个主意。谢谢!

0 个答案:

没有答案