Swift开发:Mapbox加载GeoJSON的变量作用域与数据复用问题
Got it, let's break down how to solve this—you want to load your GeoJSON once and keep it accessible across your app so you can add/remove map elements on demand, right? This is a super common pattern for optimizing Mapbox iOS apps, and here's a clean, maintainable way to do it:
We'll create a singleton class to handle loading your GeoJSON and storing the Mapbox source/layer references. Singletons ensure the data is loaded only once (when the class is first accessed) and is globally accessible anywhere in your app.
import Mapbox class MapDataManager { // Global shared instance—only one will ever exist static let shared = MapDataManager() // Store your Mapbox source and layer for easy access var geojsonSource: MGLShapeSource? var polygonLayer: MGLFillLayer? // Private initializer prevents other classes from making new instances private init() { loadGeoJSONFromBundle() } private func loadGeoJSONFromBundle() { // Replace "your-geojson-file" with your actual filename (no .geojson extension needed) guard let filePath = Bundle.main.path(forResource: "your-geojson-file", ofType: "geojson") else { print("Oops, couldn't find the GeoJSON file in your app bundle!") return } let fileURL = URL(fileURLWithPath: filePath) do { let rawData = try Data(contentsOf: fileURL) // Parse the GeoJSON into a Mapbox-compatible shape guard let parsedShape = try MGLShape(data: rawData, encoding: .utf8) else { print("Failed to parse GeoJSON data") return } // Create a shape source (mark as non-clustered since you're working with polygons) geojsonSource = MGLShapeSource(identifier: "custom-polygon-source", shape: parsedShape, options: [ .clustered: false ]) // Set up your fill layer with styling (tweak colors/opacity to match your app) polygonLayer = MGLFillLayer(identifier: "custom-polygon-layer", source: geojsonSource!) polygonLayer?.fillColor = NSExpression(forConstantValue: UIColor.systemBlue.withAlphaComponent(0.3)) polygonLayer?.fillOutlineColor = NSExpression(forConstantValue: UIColor.systemBlue) } catch { print("Error loading GeoJSON: \(error.localizedDescription)") } } // Helper method to update the entire GeoJSON shape func updateGeoJSON(with newShape: MGLShape) { DispatchQueue.main.async { self.geojsonSource?.shape = newShape } } // Helper method to add a single feature to the existing source func addFeature(_ feature: MGLFeature) { DispatchQueue.main.async { guard let source = self.geojsonSource, let currentCollection = source.shape as? MGLShapeCollection else { return } var updatedFeatures = currentCollection.shapes as! [MGLFeature] updatedFeatures.append(feature) let newCollection = MGLShapeCollectionFeature(shapes: updatedFeatures) source.shape = newCollection } } // Helper method to delete a feature by its identifier func deleteFeature(with identifier: String) { DispatchQueue.main.async { guard let source = self.geojsonSource, let currentCollection = source.shape as? MGLShapeCollection else { return } let filteredFeatures = currentCollection.shapes.filter { guard let feature = $0 as? MGLFeature else { return true } return feature.identifier as? String != identifier } let newCollection = MGLShapeCollectionFeature(shapes: filteredFeatures) source.shape = newCollection } } }
Now, in your map view controller, you just need to add the pre-loaded source and layer to your Mapbox map once it finishes loading. No more reloading GeoJSON every time the view loads!
import Mapbox class MapViewController: UIViewController, MGLMapViewDelegate { private var mapView: MGLMapView! override func viewDidLoad() { super.viewDidLoad() // Initialize your Mapbox map mapView = MGLMapView(frame: view.bounds, styleURL: MGLStyle.streetsStyleURL) mapView.delegate = self mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight] view.addSubview(mapView) } func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) { let dataManager = MapDataManager.shared // Add the source and layer to the map only if they aren't already present guard let source = dataManager.geojsonSource, let layer = dataManager.polygonLayer else { return } if !style.sources.contains(source) { style.addSource(source) } if !style.layers.contains(layer) { // Insert the polygon layer below road labels for better visibility if let roadLabelLayer = style.layer(withIdentifier: "road-label") { style.insertLayer(layer, below: roadLabelLayer) } else { style.addLayer(layer) } } } // Example: Add a new polygon when a button is tapped @IBAction func addNewPolygonTapped(_ sender: UIButton) { // Create a sample polygon feature let coordinates = [ CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194), CLLocationCoordinate2D(latitude: 37.7849, longitude: -122.4194), CLLocationCoordinate2D(latitude: 37.7849, longitude: -122.4094), CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4094), CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194) ] let newPolygon = MGLPolygonFeature(coordinates: coordinates, count: 5) newPolygon.identifier = "user-added-polygon-1" // Use the data manager to add it to the map MapDataManager.shared.addFeature(newPolygon) } // Example: Delete the sample polygon func deleteSamplePolygon() { MapDataManager.shared.deleteFeature(with: "user-added-polygon-1") } }
- Bundle Setup: Double-check that your GeoJSON file is added to your app's target (select the file in Xcode, then check the "Target Membership" box for your app).
- Layer Types: If your GeoJSON has points or lines instead of polygons, swap
MGLFillLayerforMGLPointAnnotationLayerorMGLLineLayerrespectively. - Thread Safety: All Mapbox style updates must happen on the main thread—hence the
DispatchQueue.main.asynccalls in the helper methods. - Scalability: If your app gets more complex, you could extend this manager to handle multiple GeoJSON sources or caching, but for most use cases, this singleton pattern works perfectly.
内容的提问来源于stack exchange,提问作者jingo_man

