map (AGSMap) become nil

720
2
Jump to solution
03-14-2022 11:11 PM
AfrozAlam
New Contributor III

I am creating a Dynamic map app. I am trying to implement Basemap changer through a list of base map collection of images. It is working fine but when the same base map selected twice then app crashes and showing map is nil.

I am attaching the screenshots along with code.  It would very much appreciated if somebody help me to get rid of this error.

Thanks in advance

Afroz Alam
Tags (2)
0 Kudos
1 Solution

Accepted Solutions
Nicholas-Furness
Esri Regular Contributor

Hi.

The problem you're seeing is that you're trying to use an AGSLayer that is already being used. In Runtime, for performance reasons, many objects cannot be used (or "owned") by more than one other object. In this case, your AGSMap owns a AGSBasemap which owns the layer you are reusing (Coast_url). Your code is trying to create a new AGSBasemap with a layer that is already "owned" by map's current basemap.

If you look in the Xcode console, you will see the following when you try to create the new AGSMap.

ArcGIS Runtime Error Occurred. Set a breakpoint on C++ exceptions to see the original callstack and context for this error: Error Domain=com.esri.arcgis.runtime.error Code=24 "Object is already owned." UserInfo={NSLocalizedFailureReason=Already owned., NSLocalizedDescription=Object is already owned., Additional Message=Already owned.}

Instead, you will need to create an entirely new AGSArcGISMapImageLayer (or AGSArcGISTiledLayer) and use that in a new AGSBasemap. You could modify your code like this to achieve this, using an enum instead of strings…

 

enum CustomBasemap {
    enum Language {
        case EN
        case AR
    }
    
    case coast
    case satellite
    case hybrid(language: Language)
    case streetmap(language: Language)
    
    func getLayer() -> AGSLayer {
        switch self {
            case .coast:
                return AGSArcGISMapImageLayer(url: URL(string:"https://services.gisqatar.org.qa/server/rest/services/Vector/Coast/MapServer")!)
            case .satellite:
                return AGSArcGISTiledLayer(url: URL(string:"https://services.gisqatar.org.qa/server/rest/services/Imagery/QatarSatelitte/MapServer")!)
            case .hybrid(let language):
                switch language {
                    case .EN:
                        return AGSArcGISTiledLayer(url:URL(string: "https://services.gisqatar.org.qa/server/rest/services/Vector/Qatar_StreetMap_Hybrid_E/MapServer")!)
                    case .AR:
                        return AGSArcGISTiledLayer(url:URL(string: "https://services.gisqatar.org.qa/server/rest/services/Vector/Qatar_StreetMap_Hybrid_Ar_Test/MapServer")!)
                }
            case .streetmap(let language):
                switch language {
                    case .EN:
                        return AGSArcGISTiledLayer(url:URL(string: "https://services.gisqatar.org.qa/server/rest/services/Vector/Qatar_StreetMap_E/MapServer")!)
                    case .AR:
                        return AGSArcGISTiledLayer(url:URL(string: "https://services.gisqatar.org.qa/server/rest/services/Vector/Qatar_StreetMap_A/MapServer")!)
                }
        }
    }
    
    func getBasemap() -> AGSBasemap {
        return AGSBasemap(baseLayer: getLayer())
    }
}

 

and then set the basemap on the existing map (which will save you creating a new map and setting initialViewpoints):

 

    var basemapType: CustomBasemap = .coast
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        self.map = AGSMap(basemap: basemapType.getBasemap())
        self.mapView.map = self.map
        self.SetMapExtent()
    }
...
    func Coast_Map_Tapped() {
        basemapType = .coast
        self.map.basemap = basemapType.getBasemap()
    }

 

Hope that helps.

View solution in original post

0 Kudos
2 Replies
Nicholas-Furness
Esri Regular Contributor

Hi.

The problem you're seeing is that you're trying to use an AGSLayer that is already being used. In Runtime, for performance reasons, many objects cannot be used (or "owned") by more than one other object. In this case, your AGSMap owns a AGSBasemap which owns the layer you are reusing (Coast_url). Your code is trying to create a new AGSBasemap with a layer that is already "owned" by map's current basemap.

If you look in the Xcode console, you will see the following when you try to create the new AGSMap.

ArcGIS Runtime Error Occurred. Set a breakpoint on C++ exceptions to see the original callstack and context for this error: Error Domain=com.esri.arcgis.runtime.error Code=24 "Object is already owned." UserInfo={NSLocalizedFailureReason=Already owned., NSLocalizedDescription=Object is already owned., Additional Message=Already owned.}

Instead, you will need to create an entirely new AGSArcGISMapImageLayer (or AGSArcGISTiledLayer) and use that in a new AGSBasemap. You could modify your code like this to achieve this, using an enum instead of strings…

 

enum CustomBasemap {
    enum Language {
        case EN
        case AR
    }
    
    case coast
    case satellite
    case hybrid(language: Language)
    case streetmap(language: Language)
    
    func getLayer() -> AGSLayer {
        switch self {
            case .coast:
                return AGSArcGISMapImageLayer(url: URL(string:"https://services.gisqatar.org.qa/server/rest/services/Vector/Coast/MapServer")!)
            case .satellite:
                return AGSArcGISTiledLayer(url: URL(string:"https://services.gisqatar.org.qa/server/rest/services/Imagery/QatarSatelitte/MapServer")!)
            case .hybrid(let language):
                switch language {
                    case .EN:
                        return AGSArcGISTiledLayer(url:URL(string: "https://services.gisqatar.org.qa/server/rest/services/Vector/Qatar_StreetMap_Hybrid_E/MapServer")!)
                    case .AR:
                        return AGSArcGISTiledLayer(url:URL(string: "https://services.gisqatar.org.qa/server/rest/services/Vector/Qatar_StreetMap_Hybrid_Ar_Test/MapServer")!)
                }
            case .streetmap(let language):
                switch language {
                    case .EN:
                        return AGSArcGISTiledLayer(url:URL(string: "https://services.gisqatar.org.qa/server/rest/services/Vector/Qatar_StreetMap_E/MapServer")!)
                    case .AR:
                        return AGSArcGISTiledLayer(url:URL(string: "https://services.gisqatar.org.qa/server/rest/services/Vector/Qatar_StreetMap_A/MapServer")!)
                }
        }
    }
    
    func getBasemap() -> AGSBasemap {
        return AGSBasemap(baseLayer: getLayer())
    }
}

 

and then set the basemap on the existing map (which will save you creating a new map and setting initialViewpoints):

 

    var basemapType: CustomBasemap = .coast
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        self.map = AGSMap(basemap: basemapType.getBasemap())
        self.mapView.map = self.map
        self.SetMapExtent()
    }
...
    func Coast_Map_Tapped() {
        basemapType = .coast
        self.map.basemap = basemapType.getBasemap()
    }

 

Hope that helps.

0 Kudos
AfrozAlam
New Contributor III

Dear Nicholas,

Thanks a lot for your solution of my problem. It is now working fine. You have explained very nicely and provided exact code. I am very grateful to you and hope you will help me in future as well.

Warm regards.

 

 

Afroz Alam
0 Kudos