Select to view content in your preferred language

Crash when supplying graphicsOverlays for MapView in latest SwiftUI SDK

1391
7
11-16-2023 05:53 PM
dmc76
by
Emerging Contributor

First off, this is my first Swift project, because I'm forced to use Swift, because your SDK doesn't support Objective-C.

I have a project that started out as the boilerplate "New Swift Project" in Xcode.  I have a ContentView that contains a MapView.  The MapView's .task loads an MMPK file that is downloaded onto the device if it isn't present already.  This is all working just fine.

However - if I specify the graphicsOverlays: parameter for the MapView (again, this is basically copied code verbatim from your tutorials - with the Model class, etc.), I get the following runtime crash:

/Users/jenkins/workspace/200.2.0/daily_swift_SDK_release/swift/ArcGIS/Sources/ArcGIS/Common/CoreMutableArray.swift:47: Fatal error: Object is already in use and may not be reused.

I have tried to create the GraphicsOverlay in every which way - with your "pierLocation" point, with no points, with GPS coordinates that match the MMPK file I've loaded.  Nothing will get me past that error.  There just doesn't seem to be anything else I can do, there's barely any code in this project and the parts that matter are straight out of the tutorial.  Any ideas?  I'm just trying to put a dot on a map at my current coordinate.

0 Kudos
7 Replies
DavidFeinzimer
Esri Contributor

Hi @dmc76, thank you for reaching out! I'd like to help you troubleshoot your problem. If possible, would you be able to post the portion of your code that will trigger the crash? Thank you.

 

In the meantime, I'd like to point you to our Show device location sample which contains complete code for your indicated use case.

0 Kudos
dmc76
by
Emerging Contributor

So, I have tracked down the issue, but I have no idea why it's happening.  I combined two sample/tutorial items, the GraphicsOverlay example and an example I found to manage layers.  When I first tried this sample, the toolbar was not showing and the Internet told me I needed to have a NavigationStack.  After I put that in there, the toolbar started working.  Then I added the GraphicsOverlay to the MapView and the crash happened.  I stripped back all the code this morning to find the issue was having the MapView inside of a NavigationStack.  I have since removed the NavigationStack and the .toolbar still works, so I'm not sure why it wasn't working before or why the NavigationStack caused it to work.  Here's an example (with the GraphicsOverlay param on the MapView commented out, uncomment to get the crash).  Again, I have never used SwiftUI before, so maybe there's some reasonable explanation why a MapView with a GraphicsOverlay can't live inside of a NavigationStack.  

//
//  ContentView.swift
//

import SwiftUI
import ArcGIS

private class Model: ObservableObject {
    let map: Map = {
        let map = Map(basemapStyle: .arcGISTopographic)
        map.initialViewpoint = Viewpoint(latitude: 34.027, longitude: -118.805, scale: 72_000)
        return map
    }()

    let graphicsOverlay: GraphicsOverlay = {
        // Create a graphics overlay.
        let overlay = GraphicsOverlay()

        // Create a point for the graphic location.
        let pierLocation = Point(x: -118.4978, y: 34.0086, spatialReference: .wgs84)

        // Create a red circle symbol.
        let redCircleSymbol = SimpleMarkerSymbol(style: .circle, color: .red, size: 10)

        // Create a graphic using the point and the symbol.
        let pierGraphic = Graphic(geometry: pierLocation, symbol: redCircleSymbol)

        // Add the graphic to the graphics overlay.
        overlay.addGraphic(pierGraphic)

        return overlay
    }()
}

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext

	@StateObject private var model = Model()
	@State private var isShowingSheet = false

    var body: some View	{
		NavigationStack
		{
			MapView(map: model.map)//, graphicsOverlays: [model.graphicsOverlay])
			.toolbar
			{
				ToolbarItem(placement: .bottomBar)
				{
					Button("Manage Layers")
					{
						self.isShowingSheet = true
					}
					.sheet(isPresented: $isShowingSheet)
					{
						ManageLayersSheetView(map: self.model.map)
					}
					.presentationDetents([.medium] )
					.presentationDragIndicator(.visible )
				}
			}
		}
    }
}

struct ManageLayersSheetView: View {
	/// The map with the operational layers.
	let map: Map

	/// The action to dismiss the manage layers sheet.
	@Environment(\.dismiss) private var dismiss

	/// An array for all the layers currently on the map.
	@State private var operationalLayers: [Layer] = []

	/// An array for all the layers removed from the map.
	@State private var removedLayers: [Layer] = []

	var body: some View {
		VStack {
			ZStack {
				Text("Manage Layers")
					.bold()
				HStack {
					Spacer()
					EditButton()
				}
			}
			.padding([.top, .leading, .trailing])

			List {
				Section {
					ForEach(operationalLayers, id: \.id) { layer in
						HStack {
							Image(systemName: "minus.circle.fill")
								.foregroundColor(.red)
								.imageScale(.large)
								.clipped()
								.onTapGesture {
									// Remove layer from map on minus press.
									map.removeOperationalLayer(layer)
									removedLayers.append(layer)
									operationalLayers.removeAll(where: { $0.id == layer.id })
								}
							Text(layer.name)
						}
					}
					.onMove { fromOffsets, toOffset in
						// Reorder the map's operational layers on list row move.
						operationalLayers.move(fromOffsets: fromOffsets, toOffset: toOffset)
						map.removeAllOperationalLayers()
						map.addOperationalLayers(operationalLayers)
					}
				} header: {
					Text("Operational Layers")
						#if targetEnvironment(macCatalyst)
						.padding(.top)
						#endif
				} footer: {
					Text("Tap \"Edit\" to reorder the layers.")
				}

				Section {
					ForEach(removedLayers, id: \.id) { layer in
						HStack {
							Image(systemName: "plus.circle.fill")
								.foregroundColor(.green)
								.imageScale(.large)
								.clipped()
								.onTapGesture {
									// Add layer to map on plus press.
									map.addOperationalLayer(layer)
									operationalLayers.append(layer)
									removedLayers.removeAll(where: { $0.id == layer.id })
								}
							Text(layer.name)
						}
					}
				} header: {
					Text("Removed Layers")
				}
			}
		}
		.background(Color(.systemGroupedBackground))
		.onAppear {
			operationalLayers = map.operationalLayers
		}
	}
}

 

0 Kudos
DavidFeinzimer
Esri Contributor

Hi @dmc76, when running your code above (first un-commenting the MapView's graphics overlay parameter) I was unable to reproduce any warnings or crashes. Were there any additional steps needed to trigger the crash or does it happen for you as soon as the view loads? Is there possibly any more code that was accidentally left out? Thank you.

0 Kudos
dmc76
by
Emerging Contributor

No, that's the entirety of the ContentView.swift file.. the rest of the project is just the boilerplate "new project" stuff from Xcode (the PersistenceController class, etc).  I replaced the swift file I've been working on with what I posted here on the forum and it still crashes when uncommenting the graphicsOverlays param.  Very strange that it's not happening for you too.  I'm on Xcode 14.3.1 for what it's worth.

0 Kudos
DavidFeinzimer
Esri Contributor

Hi @dmc76, while it's not the greatest answer, testing the exact same code on in Xcode 14.3.1 with an iOS 16.4 simulator crashes while everything runs fine in Xcode 15.0 with an iOS 17.0 simulator. This would explain why I couldn't reproduce this at first as I was testing with only the latter beforehand.

 

To the best of my knowledge there shouldn't be any issue placing a MapView within a NavigationStack so if I find out any other info or known workarounds for this scenario in iOS 16.x I'll make sure to update you in this thread.

 

David

 

Swift Known Issue #4281

DavidFeinzimer
Esri Contributor

Hi @dmc76, this issue is still being investigated. In the meantime a potential workaround would be to wrap the MapView within a VStack.

NavigationStack {
    VStack {
        MapView(…)
    }
}

 

David

JessevanderVoorn
New Contributor

FYI:

I faced the same issue, and it only fixed it for me by removing it from the NavigationStack + I had to set the graphicsOverlayProperty from this:

let routeGraphicsOverlay = GraphicsOverlay()

To This: 
let routeGraphicsOverlay: [GraphicsOverlay] = [.init()]

Because passing the graphicsOverlay to the MapView like this also crashes the App also without NavigationStack:
MapView(map: model.map, graphicsOverlay: [model.graphicsOverlay])

0 Kudos