ArcGis Map for Swift SDK giving error on adding Feature layer

457
11
Jump to solution
04-05-2024 07:37 AM
ShrutiVaman
New Contributor II

Hello,

I am trying to add Shapefile to the ArcGis map creating a Feature layer. But SDK always gives me an error saying FileNotFoundError(details: "Invalid path or missing shapefile data.") 

I am following the code from GitHub https://github.com/Esri/arcgis-maps-sdk-swift-samples

I also tried loading the same shapefiles on ArcGis online it worked. 

Can anyone help me fix this problem?

0 Kudos
3 Solutions

Accepted Solutions
Ting
by Esri Contributor
Esri Contributor

Your code logic is correct (despite some minor UI and styling issues) and I'm able to load a shapefile from the bundle with it.

The problem might be how you added the files to the bundle. If you are reading the files from the Documents folder, or using FileImporter, there shouldn't be this problem.

When adding files to an app bundle, please note the difference between Create groups vs Create folder reference, where groups has the gray folder icon in the project navigator, while a folder reference has the blue folder icon.

Ting_0-1712597867767.png

The key difference between these two is that group is an logical hierarchy, where all the files are stored at the same top level folder of the app bundle, and only grouped logically by the Xcode project file. Whereas a folder reference retains the actually folder structure in the app bundle of the added files.

Because shapefile format is not a single file - it contains various metadata and supporting files, the folder structure needs to be retained. When you add the folder of the shapefile to the project, use the Create folder references radio button.

---

Please see the code project attached for an idea. Let me know if you have more questions 🙂

View solution in original post

Ting
by Esri Contributor
Esri Contributor

My apologies for a mistake in my previous follow up answer. Instead of passing the folder path in, you'll still need to create the table from a `.shp` file.

Please see the code below 

import ArcGIS
import SwiftUI
import UniformTypeIdentifiers

struct ContentView: View {
    @State private var map: Map = {
        let map = Map(basemapStyle: .arcGISTopographic)
        map.initialViewpoint = Viewpoint(
            latitude: 56.6413,
            longitude: -3.8890,
            scale: 6e6
        )
        return map
    }()
    
    @State private var isImporting = false
    
    @State private var selectedURL: URL?
    
    var body: some View {
        MapView(map: map)
            .toolbar {
                ToolbarItem(placement: .bottomBar) {
                    Button("Load Files") {
                        isImporting = true
                    }
                }
            }
            .task(id: selectedURL) {
                if let selectedURL, selectedURL.startAccessingSecurityScopedResource() {
                    defer {
                        selectedURL.stopAccessingSecurityScopedResource()
                    }
                    do {
                        try await loadShapefile(url: selectedURL)
                    } catch {
                        // Handle failure.
                        print("Unable to read file contents")
                        print(error.localizedDescription)
                    }
                }
            }
            .fileImporter(
                isPresented: $isImporting,
                allowedContentTypes: [.shp]
            ) { result in
                switch result {
                case .success(let fileURL):
                    selectedURL = fileURL
                case .failure(let error):
                    print(error.localizedDescription)
                    print("Unable to load file from importer")
                }
            }
    }
    
    func loadShapefile(url: URL) async throws {
        // Instantiates shapefile feature table with the path to the .shp file.
        let shapefileTable = ShapefileFeatureTable(fileURL: url)
        try await shapefileTable.load()
        // Creates a feature layer for the shapefile feature table.
        let shapefileLayer = FeatureLayer(featureTable: shapefileTable)
        // Adds the layer to the map.
        map.addOperationalLayer(shapefileLayer)
    }
}

// Data for testing can be downloaded from https://www.arcgis.com/home/item.html?id=15a7cbd3af1e47cfa5d2c6b93dc44fc2

private extension UTType {
    /// A type that represents a shapefile.
    static let shp = UTType(filenameExtension: "shp")!
}

 

View solution in original post

Ting
by Esri Contributor
Esri Contributor

I'm not a GIS expert, so I don't know too much about the file structure intrinsics. To my understanding, a shapefile must contain at least 3 files: .shp, .shx, and .dbf. Your zip only has the .shp files.

Maybe ask the question in https://community.esri.com/t5/arcgis-pro-questions/bd-p/arcgis-pro-questions to have someone examine your files. Maybe the person who exported these files missed the require metadata.

View solution in original post

11 Replies
Ting
by Esri Contributor
Esri Contributor

Can you share your code or a reproducible project to load the shapefile? From the error message it is hard to tell what the exactly problem is - either the shapefile folder is missing some metadata files, or the path you passed in is not recognized. Thanks.

0 Kudos
ShrutiVaman
New Contributor II

Here is my code

struct ContentView: View {

    @State private var presentError : Bool = false

    @State private var error: Error?

    @State private var map: Map = {

        let map = Map(basemapStyle: .arcGISTopographic)

         map.initialViewpoint = Viewpoint(

             latitude: 34.723638,

             longitude: -92.500932,

             scale: 72_000

         )

         return map

    }()

    var body: some View {

        MapView(map: map)

            .toolbar(content: {

                ToolbarItem(placement: .bottomBar,content: {

                    Text("Load Shapefile")

                        .task {

                            do{

                                try await LoadShapefile()

                            }catch{

                                print(error)

                                self.error = error

                            }

                            

                        }

                })

            })

            .onAppear{

                ArcGISEnvironment.authenticationManager.arcGISAuthenticationChallengeHandler = ChallengeHandler()

            }

            .onDisappear{

                ArcGISEnvironment.authenticationManager.arcGISAuthenticationChallengeHandler = nil

            }

            .alert(isPresented: $presentError){

                Alert(title: Text("Something went wrong"), message: Text(error!.localizedDescription))

            }

            

    }

    func LoadShapefile() async throws{

        print("shapefile ",URL.shapefilePath.absoluteString)

        // Instantiates shapefile feature table with the path to the .shp file.

        let shapefileTable = ShapefileFeatureTable(fileURL: .shapefilePath)

        try await shapefileTable.load() // Throwing error

        // Creates a feature layer for the shapefile feature table.

        let shapefileLayer = FeatureLayer(featureTable: shapefileTable)

        // Adds the layer to the map.

        map.addOperationalLayer(shapefileLayer)

    }

    private struct ChallengeHandler: ArcGISAuthenticationChallengeHandler {

        func handleArcGISAuthenticationChallenge(

            _ challenge: ArcGISAuthenticationChallenge

        ) async throws -> ArcGISAuthenticationChallenge.Disposition {

            // NOTE: Never hardcode login information in a production application.

            // This is done solely for the sake of the sample.

            return .continueWithCredential(

                try await TokenCredential.credential(for: challenge, username: "myusername", password: "my password")

            )

        }

    }

}

 

private extension URL{

    static var shapefilePath : URL { Bundle.main.url(forResource: "test", withExtension: "shp")! }

}

I have also added files under the Copy Bundle Resource in Build Phase.

I tried reading another text file using Bundle. It worked fine. Just the shape file is giving an error. 

0 Kudos
Ting
by Esri Contributor
Esri Contributor

Your code logic is correct (despite some minor UI and styling issues) and I'm able to load a shapefile from the bundle with it.

The problem might be how you added the files to the bundle. If you are reading the files from the Documents folder, or using FileImporter, there shouldn't be this problem.

When adding files to an app bundle, please note the difference between Create groups vs Create folder reference, where groups has the gray folder icon in the project navigator, while a folder reference has the blue folder icon.

Ting_0-1712597867767.png

The key difference between these two is that group is an logical hierarchy, where all the files are stored at the same top level folder of the app bundle, and only grouped logically by the Xcode project file. Whereas a folder reference retains the actually folder structure in the app bundle of the added files.

Because shapefile format is not a single file - it contains various metadata and supporting files, the folder structure needs to be retained. When you add the folder of the shapefile to the project, use the Create folder references radio button.

---

Please see the code project attached for an idea. Let me know if you have more questions 🙂

ShrutiVaman
New Contributor II

Thank you so much. It worked. My main use of this app is to allow users to download the Shapefile and read it. Do I have to ask the user to download the folder containing .shp, .prj, .dbf, .shx files?

Ting
by Esri Contributor
Esri Contributor

Yes. See this doc for details. It would be good to be a zip file with an enclosing folder that contains all the components.

0 Kudos
ShrutiVaman
New Contributor II

I tried this and it didn't work. Always get an error saying

ArcGISError(code: 10012, description: "Unknown error.", details: "boost::filesystem::status: Operation not permitted [system:1])

0 Kudos
Ting
by Esri Contributor
Esri Contributor

Can you share the specifics?

Do you mean you tried to use the path to a zip file to create a shapefile? That won't work. It still needs to be extracted to a folder.

If the shapefile folder is coming from app's Documents folder or user's file importer, to get the folder structure to create a shapefile, the best would be acquiring the enclosing folder's path.

0 Kudos
ShrutiVaman
New Contributor II

I am trying to import the shapefile from the file importer and using the URL to load shape file. Here is the code.

 .fileImporter(

                isPresented: $isImporting,

                allowedContentTypes: [.folder,.zip,.item],

                allowsMultipleSelection: false,

                onCompletion: { result in

                    switch result{

                    case .success(let file):

                        isChangingView = true

                        print("file ",file)

                        do {

                            

                            guard let selectedFile: URL = try result.get().first else { return }

                            let gotAccess = selectedFile.startAccessingSecurityScopedResource()

                            if !gotAccess { return }

                            self.selectedFile = selectedFile

                            LoadShapefile(self.selectedFile)

                            selectedFile.stopAccessingSecurityScopedResource()

                                                            

                        }catch {

                            // Handle failure.

                            print("Unable to read file contents")

                            print(error.localizedDescription)

                        }

                        

                    case .failure(let error):

                        print("error ",error.localizedDescription)

                    }

 

                })

 

func LoadShapefile(shapefilePath:URL) async throws{

        print("shapefile ",shapefilePath?.absoluteString)

        // Instantiates shapefile feature table with the path to the .shp file.

        let shapefileTable = ShapefileFeatureTable(fileURL: self.shapefilePath!)

        try await shapefileTable.load()

        // Creates a feature layer for the shapefile feature table.

        let shapefileLayer = FeatureLayer(featureTable: shapefileTable)

 

        // Adds the layer to the map.

        map.addOperationalLayer(shapefileLayer)

    }

 

This is giving me the error  ArcGISError(code: 10012, description: "Unknown error.", details: "boost::filesystem::status: Operation not permitted [system:1]) 

0 Kudos
Ting
by Esri Contributor
Esri Contributor

My apologies for a mistake in my previous follow up answer. Instead of passing the folder path in, you'll still need to create the table from a `.shp` file.

Please see the code below 

import ArcGIS
import SwiftUI
import UniformTypeIdentifiers

struct ContentView: View {
    @State private var map: Map = {
        let map = Map(basemapStyle: .arcGISTopographic)
        map.initialViewpoint = Viewpoint(
            latitude: 56.6413,
            longitude: -3.8890,
            scale: 6e6
        )
        return map
    }()
    
    @State private var isImporting = false
    
    @State private var selectedURL: URL?
    
    var body: some View {
        MapView(map: map)
            .toolbar {
                ToolbarItem(placement: .bottomBar) {
                    Button("Load Files") {
                        isImporting = true
                    }
                }
            }
            .task(id: selectedURL) {
                if let selectedURL, selectedURL.startAccessingSecurityScopedResource() {
                    defer {
                        selectedURL.stopAccessingSecurityScopedResource()
                    }
                    do {
                        try await loadShapefile(url: selectedURL)
                    } catch {
                        // Handle failure.
                        print("Unable to read file contents")
                        print(error.localizedDescription)
                    }
                }
            }
            .fileImporter(
                isPresented: $isImporting,
                allowedContentTypes: [.shp]
            ) { result in
                switch result {
                case .success(let fileURL):
                    selectedURL = fileURL
                case .failure(let error):
                    print(error.localizedDescription)
                    print("Unable to load file from importer")
                }
            }
    }
    
    func loadShapefile(url: URL) async throws {
        // Instantiates shapefile feature table with the path to the .shp file.
        let shapefileTable = ShapefileFeatureTable(fileURL: url)
        try await shapefileTable.load()
        // Creates a feature layer for the shapefile feature table.
        let shapefileLayer = FeatureLayer(featureTable: shapefileTable)
        // Adds the layer to the map.
        map.addOperationalLayer(shapefileLayer)
    }
}

// Data for testing can be downloaded from https://www.arcgis.com/home/item.html?id=15a7cbd3af1e47cfa5d2c6b93dc44fc2

private extension UTType {
    /// A type that represents a shapefile.
    static let shp = UTType(filenameExtension: "shp")!
}