使用 DocumentPicker SwiftUI 更改状态

Change State with DocumentPicker SwiftUI

我试图在使用 documentPicker 选择文档后显示列表视图。出现以下错误...

Fatal error: No ObservableObject of type Switcher found.
A View.environmentObject(_:) for Switcher may be missing as an ancestor of this view.: file /BuildRoot/Library/Caches/com.apple.xbs/Sources/Monoceros/Monoceros-30.4/Core/EnvironmentObject.swift, line 55

看来我应该使用 EnviromentObject 绑定让所有视图都能够读取、访问和更新切换器 class。在 CSVDocumentPicker.swift 中的协调器 Class 下似乎出了问题。
我正在使用 @EnvironmentObject var switcher:Switcher 并使用 documentPicker 函数来切换切换器状态,以便显示列表视图。我被难住了。

SceneDelegate.swift

import UIKit
import SwiftUI


class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?
    var switcher = Switcher()

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        let contentView = ContentView().environmentObject(Switcher())

        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView.environmentObject(switcher))


            self.window = window
            window.makeKeyAndVisible()
        }
    }

    func sceneDidDisconnect(_ scene: UIScene) {

    }

    func sceneDidBecomeActive(_ scene: UIScene) {

    }

    func sceneWillResignActive(_ scene: UIScene) {

    }

    func sceneWillEnterForeground(_ scene: UIScene) {

    }

    func sceneDidEnterBackground(_ scene: UIScene) {

    }


}

CSVDocumentPicker.swift

import Combine
import SwiftUI

class Switcher: ObservableObject {
var didChange = PassthroughSubject<Bool, Never>()
  var isEnabled = false {
      didSet {
          didChange.send(self.isEnabled)
      }
  }
}


struct CSVDocumentPicker: View {
  @EnvironmentObject var switcher:Switcher
    @State private var isPresented = false

    var body: some View {
        VStack{
            Text("csvSearch")
                Button(action: {self.isPresented = true
                })
                {Text("import")
                Image(systemName: "folder").scaledToFit()
                }.sheet(isPresented: $isPresented) {
                    () -> DocumentPickerViewController in
                    DocumentPickerViewController.init(onDismiss: {
                    self.isPresented = false
                    })
            }
            if switcher.isEnabled  {
                        ListView()
                     }

        }
    }
}

struct CSVDocumentPicker_Previews: PreviewProvider {
    static var previews: some View {
        CSVDocumentPicker().environmentObject(Switcher())
    }
}
/// Wrapper around the `UIDocumentPickerViewController`.
struct DocumentPickerViewController {

    private let supportedTypes: [String] = ["public.item"]

    // Callback to be executed when users close the document picker.
    private let onDismiss: () -> Void

    init(onDismiss: @escaping () -> Void) {
        self.onDismiss = onDismiss
    }
}

// MARK: - UIViewControllerRepresentable

extension DocumentPickerViewController: UIViewControllerRepresentable {

    typealias UIViewControllerType = UIDocumentPickerViewController

    func makeUIViewController(context: Context) -> DocumentPickerViewController.UIViewControllerType {
        let documentPickerController = UIDocumentPickerViewController(documentTypes: supportedTypes, in: .import)
        documentPickerController.allowsMultipleSelection = false
        documentPickerController.delegate = context.coordinator
        return documentPickerController
    }

    func updateUIViewController(_ uiViewController: DocumentPickerViewController.UIViewControllerType, context: Context) {}

    // MARK: Coordinator

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, UIDocumentPickerDelegate, ObservableObject {
        @EnvironmentObject var switcher:Switcher
        var parent: DocumentPickerViewController

        init(_ documentPickerController: DocumentPickerViewController) {
            parent = documentPickerController
        }

        func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {

            globalPathToCsv = url
            loadCSV()

         switcher.isEnabled.toggle()
          }

        func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
            parent.onDismiss()

        }
    }
}

ContentView.swift

import SwiftUI
import UIKit

var globalPathToCsv:URL!
var csvArray = [[String:String]]()
var csv = CSVaccessability()


func loadCSV(){
    csv.csvToList()
  // print(csvArray)

}
struct ContentView: View {
 @EnvironmentObject var switcher:Switcher

      var body: some View {
        VStack{
            CSVDocumentPicker().environmentObject(Switcher())

                                    }
                            }
                    }

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environmentObject(Switcher())
    }
}

ListView.swift

import SwiftUI

struct ListView: View {
    var body: some View {
        HStack{
        List {
            ForEach(csvArray, id:\.self) { dict in Section {DataList(dict: dict)} }
        }
        }}
}

struct DataList : View {
  @State var dict = [String: String]()
    var body: some View {
        let keys = dict.map{[=14=].key}
        let values = dict.map {[=14=].value}

        return  ForEach(keys.indices) {index in
            HStack {
                Text(keys[index])
                Text("\(values[index])")
            }
        }
    }
}

struct ListView_Previews: PreviewProvider {
    static var previews: some View {
        ListView()
    }
}

CSVaccessability.swift

import Foundation
import SwiftCSV

var csvData:[[String]]!
var headers:[String] = []


class CSVaccessability {

    var numberOfColumns:Int!
    var masterList = [[String:Any]]()


    func csvToList(){
        if let url = globalPathToCsv {
                    do {
                        print(url)
                        let csvFile: CSV = try CSV(url: globalPathToCsv)
                        let csv = csvFile
                        //print(stream)
                        //print(csvFile)
                        headers = csv.header
                        csvArray=csv.namedRows
                            } catch {print("contents could not be loaded")}}
                               else {print("the URL was bad!")}

    }
}

我已经为这个项目导入了 SwiftCSV...

你用过这个吗?我发现的唯一问题是 ContentView 中的 var csv = CSVaccessability() 行。 CSVaccessability 不存在。

创建了一个新的 class... ToggleView.swift

import Foundation
class ToggleView: ObservableObject {
    @Published var toggleView: Bool = false

}

将环境对象添加到 ContentView.swift @EnvironmentObject var viewToggle: ToggleView

还将 .environmentObject(ToggleView()) 添加到将被调用并导致崩溃的任何视图中,崩溃日志对此有所帮助...

          Text("csvSearch")
                           Button(action: {self.isPresented = true
                            self.viewToggle.toggleView.toggle()
                           // self.switcher = true
                           })
                           {Text("import")
                           Image(systemName: "folder").scaledToFit()
                           }.sheet(isPresented: $isPresented) {
                               () -> DocumentPickerViewController in
                            DocumentPickerViewController.init()
                                                  }
            if self.picker {
                DocumentPickerViewController().environmentObject(ToggleView())
            }

            if self.viewToggle.toggleView{
                ListView()
                        }
                   }
               }
            }

这是我针对 Mac 的 Catalyst 应用程序的解决方案,但为了避免再次按下 Image (systemName: "book") 按钮来更新文本字段中的数据,我实施了GeoFolderReadFileView 中的隐藏视图以强制更新视图。

//文件:GeoFolderCodStruct

import Foundation

struct GeoFolderCodStruct:Codable {
    var isActive:Bool = true

    var dataCreazione:Date = Date()

    var geoFolderPath:String = ""
    var nomeCartella:String = ""
    var nomeCommittente:String = ""
    var nomeArchivio:String = ""
    var note:String = ""


    var latitudine:Double? = nil
    var longitudine:Double? = nil
    var radiusCircle:Int16? = nil
    //Roma 42.1234 13.1234
}

//文件:PickerForReadFile

import SwiftUI

final class PickerForReadFile: NSObject, UIViewControllerRepresentable, ObservableObject {
    @Published var geoFolder = GeoFolderCodStruct()

    lazy var viewController:UIDocumentPickerViewController = {
        let vc = UIDocumentPickerViewController(documentTypes: ["geof"], in: .open)
        vc.allowsMultipleSelection = false
        vc.delegate = self
        return vc
    }()

    func makeUIViewController(context: UIViewControllerRepresentableContext<PickerForReadFile>) -> UIDocumentPickerViewController {
        viewController.delegate = self
        return viewController
    }

    func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: UIViewControllerRepresentableContext<PickerForReadFile>) {
        print("updateUIViewController")
    }
}

extension PickerForReadFile: UIDocumentPickerDelegate {
    func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
        if urls.count > 0 {
            DispatchQueue.main.async {
                let url = urls[0]
                do {
                    let data = try Data(contentsOf: url)
                    let decoder = JSONDecoder()
                    decoder.dateDecodingStrategy = .formatted(dateFormatter)
                    let jsonData = try decoder.decode(GeoFolderCodStruct.self, from: data)
                    self.geoFolder = jsonData
                    print("geoFolder: \(self.geoFolder.nomeArchivio)")
                } catch {
                    print("error:\(error)")
                }
            }
        }
    }

    func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
        controller.dismiss(animated: true) {
            print("Cancel from picker view controller")
        }
    }
}

//文件:GeoFolderReadFileView

import SwiftUI

struct GeoFolderReadFileView: View {

    @ObservedObject var picker = PickerForReadFile()
    @Binding var geoFolder: GeoFolderCodStruct

var body: some View {
        VStack(alignment: .trailing){
            Button(action: {
                #if targetEnvironment(macCatalyst)
                print("Press open file")
                let vc = UIApplication.shared.windows[0].rootViewController!
                vc.present(self.picker.viewController, animated: true) {
                    self.geoFolder = self.picker.geoFolder
                }
                #endif
            }) {
                Image(systemName: "book")
            }

            urlPickedView()
                .hidden()
        }
    }

private func urlPickedView() -> some View {
        DispatchQueue.main.async {
            let geoF = self.picker.geoFolder
            print("Committente: \(geoF.nomeCommittente) - Archivio: \(geoF.nomeArchivio)")
            self.geoFolder = geoF
        }
        return TextField("", text: $geoFolder.geoFolderPath)
    }
}

//文件:ContentView

    import SwiftUI

    struct ContentView: View {

    @State private var geoFolder = GeoFolderCodStruct()

    var body: some View {
        VStack {
            HStack {
                Text("Open GeoFolder File:")
                    .padding()
                Spacer()
                GeoFolderReadFileView(geoFolder: $geoFolder)
                    .padding()
            }
        .padding()

            Group {
                TextField("", text: $geoFolder.geoFolderPath)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .padding()
                TextField("", text: $geoFolder.nomeCartella)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .padding()
                TextField("", text: $geoFolder.nomeCommittente)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .padding()
                TextField("", text: $geoFolder.nomeArchivio)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .padding()
                TextField("", text: $geoFolder.note)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .padding()
            }
            .padding()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

最后一个 json 文件,用于测试代码。

{
  "nomeCommittente" : "Appple",
  "note" : "Image from Cupertino (CA).",
  "latitudine" : 37.332161,
  "longitudine" : -122.030352,
  "nomeCartella" : "Foto",
  "geoFolderPath" : "\/Users\/cesare\/Desktop",
  "radiusCircle" : 50,
  "dataCreazione" : "20\/03\/2020",
  "nomeArchivio" : "AppleCampus-Image",
  "isActive" : true
}

我无法实施@Mdoyle1 提出的解决方案。我希望有人可以编辑代码使其正常工作,而无需创建隐藏视图。