使用 DocumentPicker SwiftUI 更改状态

Posted

技术标签:

【中文标题】使用 DocumentPicker SwiftUI 更改状态【英文标题】:Change State with DocumentPicker SwiftUI 【发布时间】:2019-10-11 19:05:27 【问题描述】:

我正在尝试在使用 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 绑定来让所有视图都能够读取、访问和更新 Switcher 类。在 CSVDocumentPicker.swift 中的 Coordinator 类下似乎出了点问题。 我正在使用 @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$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!")

    

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

【问题讨论】:

【参考方案1】:

创建了一个新类... 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()
                        
                   
               
            

【讨论】:

【参考方案2】:

你有没有得到这个工作?我发现的唯一问题是 ContentView 中的 var csv = CSVaccessability() 行。 CSV 可访问性不存在。

【讨论】:

我还没有让它工作。我在上面添加了 CSVaccessability。仍在寻找解决方案! 在我的情况下,协调器对象被释放,并且在调用documentPicker(didPickDocumentsAt: [URL]) 方法之前调用了 didCancel 方法,我不知道如何为协调器提供强引用以防止其释放。跨度> 【参考方案3】:

这是我针对 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)
    

//文件:内容视图

    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 提出的解决方案。我希望有人可以编辑代码以使其正常工作,而无需创建隐藏视图。

【讨论】:

以上是关于使用 DocumentPicker SwiftUI 更改状态的主要内容,如果未能解决你的问题,请参考以下文章

(Swift) 如何获取 documentPicker URL 的数据表示

SWIFT - NSCocoaErrorDomain,无法从 documentPicker 打开文档

Xamarin iOS DocumentPicker:如何导入大文件?

Expo大作战(三十九)--expo sdk api之 DocumentPicker,Contacts(获取手机联系人信息),Branch

UIDocumentPickerViewController 用法

从 URL 呈现 Files 文档