2

I'm trying to get a List View to appear after selecting a document with documentPicker. Getting the following error...

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

It seems like I should use an EnviromentObject binding to have all views be able to read, access and update the Switcher class. Under the Coordinator Class in CSVDocumentPicker.swift is where things seem to go wrong.
I'm using @EnvironmentObject var switcher:Switcher and using the documentPicker function to toggle the switcher state so the Lists View will be displayed. I'm stumped.

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{$0.key}
        let values = dict.map {$0.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!")}

    }
}

I've imported SwiftCSV for this project...

Mdoyle1
  • 121
  • 1
  • 12

3 Answers3

1

Created a new class... ToggleView.swift

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

}

Added the Environment Object to ContentView.swift @EnvironmentObject var viewToggle: ToggleView

Also added .environmentObject(ToggleView()) to any view that would be called and cause a crash the crash logs helped with this...

          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()
                        }
                   }
               }
            }
Mdoyle1
  • 121
  • 1
  • 12
0

Did you ever get this working? The only problem I found was with the line var csv = CSVaccessability() in the ContentView. CSVaccessability does not exist.

  • I haven't gotten it working yet. I've added CSVaccessability above. Still looking for a solution! – Mdoyle1 Oct 28 '19 at 02:50
  • in my case the coordinator object gets deallocated and didCancel method is invoked before calling `documentPicker(didPickDocumentsAt: [URL])` method, i don't know how to make a strong reference for the coordinator to prevent it from deallocating. – JAHelia Mar 04 '20 at 16:42
0

This is my solution for the Catalyst app for Mac, but to avoid pressing theImage (systemName: "book")button a second time to update the data in the text fields, I have implemented a hidden view in GeoFolderReadFileView to force the view update.

//File: 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
}

//File: 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")
        }
    }
}

//File: 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)
    }
}

//File: 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()
    }
}

and the last, the json file to read for test the code.

{
  "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
}

I was unable to implement the solution proposed by @Mdoyle1. I hope someone can edit the code to make it work as it should, without create hidden view.