Skip to content

App freezes when trying to dismiss a popup in a sheet #1092

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
zkline101 opened this issue Mar 4, 2025 · 9 comments
Closed

App freezes when trying to dismiss a popup in a sheet #1092

zkline101 opened this issue Mar 4, 2025 · 9 comments
Labels
bug Something isn't working

Comments

@zkline101
Copy link
Contributor

I have an iOS app that is showing a popup in a sheet and when I try to dismiss that sheet by swiping down the whole app freezes and I see the memory starts to increase without stopping.

I have a reproducer for this that I can send if that helps.

@zkline101 zkline101 added the bug Something isn't working label Mar 4, 2025
@zkline101
Copy link
Contributor Author

I am using an iOS 18.2 simulator, Xcode 16.2 and I am using the Toolkit branch OfflineMapAreasView and I am using 200.6 of the SDK.

@zkline101
Copy link
Contributor Author

In my app, if I remove the scale bar from the app then this problem doesn't occur. This could be an iOS bug but not sure. It seems like if I have some toolkit components overlaid on my map then I show a popup in a sheet and I try to swipe to dismiss the sheet then it freezes.

@dfeinzimer
Copy link
Collaborator

Interestingly, not a problem with a floating panel, just sheets.

Reproducer
import ArcGIS
import ArcGISToolkit
import SwiftUI

struct PopupExampleView: View {
    static func makeMap() -> Map {
        let portalItem = PortalItem(
            portal: .arcGISOnline(connection: .anonymous),
            id: Item.ID("9f3a674e998f461580006e626611f9ad")!
        )
        return Map(item: portalItem)
    }
    
    /// The height of the map view's attribution bar.
    @State private var attributionBarHeight: CGFloat = 0
    
    /// The `Map` displayed in the `MapView`.
    @State private var map = makeMap()
    
    /// The point on the screen the user tapped on to identify a feature.
    @State private var identifyScreenPoint: CGPoint?
    
    /// The popup to be shown as the result of the layer identify operation.
    @State private var popup: Popup? {
        didSet { showPopup = popup != nil }
    }
    
    /// A Boolean value specifying whether the popup view should be shown or not.
    @State private var showPopup = false
    
    /// The detent value specifying the initial `FloatingPanelDetent`.  Defaults to "full".
    @State private var floatingPanelDetent: FloatingPanelDetent = .full
    
    private let alignment: Alignment = .bottomLeading
    private let maxWidth: Double = 175.0
    @State private var spatialReference: SpatialReference?
    @State private var unitsPerPoint: Double?
    @State private var viewpoint: Viewpoint?
    
    var body: some View {
        MapViewReader { proxy in
            MapView(map: map)
                .onSpatialReferenceChanged { spatialReference = $0 }
                .onUnitsPerPointChanged { unitsPerPoint = $0 }
                .onViewpointChanged(kind: .centerAndScale) { viewpoint = $0 }
                .onAttributionBarHeightChanged {
                    attributionBarHeight = $0
                }
                .onSingleTapGesture { screenPoint, _ in
                    identifyScreenPoint = screenPoint
                }
                .task(id: identifyScreenPoint) {
                    guard let identifyScreenPoint else { return }
                    let identifyResult = try? await proxy.identifyLayers(
                        screenPoint: identifyScreenPoint,
                        tolerance: 10,
                        returnPopupsOnly: true
                    ).first
                    popup = identifyResult?.popups.first
                }
                .overlay(alignment: alignment) {
                    Scalebar(
                        maxWidth: maxWidth,
                        spatialReference: spatialReference,
                        unitsPerPoint: unitsPerPoint,
                        viewpoint: viewpoint
                    )
                    .padding(.horizontal, 10)
                    .padding(.vertical, 10 + attributionBarHeight)
                }
                .sheet(
                    isPresented: $showPopup
                ) { [popup] in
                    PopupView(popup: popup!, isPresented: $showPopup)
                        .padding()
                }
        }
    }
}

@rolson
Copy link
Contributor

rolson commented Mar 7, 2025

This is bad bug affecting the epic app and other dev summit demos.

@mhdostal mhdostal pinned this issue Mar 7, 2025
@mhdostal
Copy link
Member

mhdostal commented Mar 7, 2025

It does seem like SwiftUI is struggling with the Scalebar. I got it to freeze with @dfeinzimer 's reproducer. Breaking in the debugger produce the results below (this is just a representative sample, not all of them):

Image

Image

Image

I have not been able to reproduce with either the Compass or OverviewMap components in an overlay, just the Scalebar.

Is removing the scalebar an acceptable solution for now? I suspect it will take some time to diagnose the issue with the Scalebar and (probably) attempt to simplify it.

@dfeinzimer
Copy link
Collaborator

This seems possibly related to #802.

Another workaround would be to remove the Scalebar from the hierarchy while the sheet is presented.

.overlay {
    if !sheetIsPresented {
        Scalebar(
            ...
        )
    }
}

@rolson
Copy link
Contributor

rolson commented Mar 7, 2025

I tried that in my app and it never came back... strange. Yes, probably best to just remove scalebar.

cc @nixta

@dfeinzimer
Copy link
Collaborator

Well, this is weird. Using overlay(alignment:content:) vs overlay(content:) seems to make all the difference. The alignment is throwing something off somehow. I've distilled the reproducer down to this:

import ArcGIS
import ArcGISToolkit
import SwiftUI

struct PopupExampleView: View {
    let map = Map(basemapStyle: .arcGISCommunity)
    
    @State private var showPopup = false
    
    @State private var spatialReference: SpatialReference?
    @State private var unitsPerPoint: Double?
    @State private var viewpoint: Viewpoint?
    
    var body: some View {
        MapView(map: map)
            .onSpatialReferenceChanged { spatialReference = $0 }
            .onUnitsPerPointChanged { unitsPerPoint = $0 }
            .onViewpointChanged(kind: .centerAndScale) { viewpoint = $0 }
            .onSingleTapGesture { _, _ in
                showPopup = true
            }
            .overlay {                          // <=== NO FREEZE
//            .overlay(alignment: .bottomLeading) { // <=== FREEZES
                Scalebar(
                    maxWidth: 175,
                    spatialReference: spatialReference,
                    unitsPerPoint: unitsPerPoint,
                    viewpoint: viewpoint
                )
            }
            .sheet(isPresented: $showPopup) { }
    }
}

@rolson
Copy link
Contributor

rolson commented Mar 8, 2025

Wow, nice! We can work with that. Great find.

@rolson rolson closed this as completed Mar 10, 2025
@dfeinzimer dfeinzimer unpinned this issue Mar 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants