mapbox / mapbox-maps-ios

Interactive, thoroughly customizable maps for iOS powered by vector tiles and Metal
https://www.mapbox.com/mapbox-mobile-sdk
Other
450 stars 148 forks source link

Snapshotter.start(overlayHandler:completion:) completion blocks never get called back #1061

Open Rheaparks opened 2 years ago

Rheaparks commented 2 years ago

Environment

Observed behavior and steps to reproduce

When using the Snapshotter, neither one of its completion blocks are called. Here's some sample code:

struct SnapshotMap: View {
    private let snapshotter = Snapshotter(
        options: .init(size: CGSize(width: 40, height: 40),
                       pixelRatio: UIScreen.main.scale,
                       glyphsRasterizationOptions: .init(),
                       resourceOptions: MapViewController.myResourceOptions,
                       showsLogo: false,
                       showsAttribution: false))

    @State private var mapImage: SwiftUI.Image? = nil

    var body: some View {
        if let mapImage = mapImage {
            mapImage
                .resizable()
                .scaledToFill()
                .frame(height: 200)
                .clipped()

        } else {
            ZStack {
                Color.clear
                    .frame(height: 200)
                    .overlay {
                        GeometryReader { geometry in
                            Color.clear
                                .task {
                                    makeSnapshot(size: geometry.size)
                                }
                        }
                    }

                ProgressView()
            }
        }
    }

    func makeSnapshot(size: CGSize) {
        snapshotter.snapshotSize = size

        snapshotter.setCamera(to: .init(center: CLLocationCoordinate2D(latitude: 4, longitude: 4), padding: nil, anchor: CGPoint(x: 4, y: 4), zoom: 6, bearing: nil, pitch: nil))

        snapshotter.start { overlay in
            print(overlay)
        } completion: { result in
            switch result {
            case let .success(image):
                mapImage = Image(uiImage: image)
            case let .failure(error):
                print(error.localizedDescription)
            }
        }
    }
}

struct SnapshotMap_Previews: PreviewProvider {
    static var previews: some View {
        SnapshotMap()
    }
}

Here, any completion blocks inside makeSnapshot(size:) are never called. There are no errors printed or returned, it just fails silently.

Expected behavior

I'd expect at least an error message to be printed, and more importantly, I would expect the completion blocks to be returned with an error, or even succeed.

Notes / preliminary analysis

This sample code uses SwiftUI. I didn't try the Snapshotter in a UIKit project / component. Maybe it works better there, I don't know.

I tried to put some breakpoints inside the start(overlayHandler:completion:) method in Snapshotter. The code here is executed, but things start to go awry when MapSnapshotter.start is called. It's in this completion block that nothing ever gets executed.

SzymonMatysik commented 1 year ago

Any news on this topic? I Have same issue on UIKit project,start never produces result in completion.

tom-skydio commented 1 year ago

Also seeing this

Juju31Fr commented 1 year ago

Same here with SwiftUI. Can someone from Mapbox do a check ?

tom-skydio commented 1 year ago

respectfully the v6->v10 migration is the most frustrating migration I have ever done. half the code is undocumented and its really annoying when stuff like this doesn't work

here's the fix:

    // if you dont set this, the snapshotter result closure is never called, lmao!
    snapshotter.style.uri = mapView.mapboxMap.style.uri

dont forget to do this or it takes a default screenshot of Brooklyn.

    let cameraOptions = CameraOptions(cameraState: self.mapView.cameraState)
    snapshotter.setCamera(to: cameraOptions)

my tips for people doing the migration

Rheaparks commented 1 year ago

Thank you @tom-skydio! Your answer is coming just as I'm going to seriously need the feature! I'll be sure to try your tips ☺️

Juju31Fr commented 1 year ago

@tom-skydio you are lucky. On my side, even with style URI and camera set-up, the completion block is never called ... Are you using it with SwiftUI ?

tom-skydio commented 1 year ago

fwiw @Juju31Fr im using 10.9.0-rc.1 and no im using it within UIKit

Juju31Fr commented 1 year ago

Ok. I’m using 10.8.1 with SwiftUI. @RheaParks you will probably be able to confirm as you are using SwiftUI as well.

Juju31Fr commented 1 year ago

OK This works now. Everything has to be done in the main thread.

haikallf commented 8 months ago
let snapshotter: Snapshotter = Snapshotter(options: options)
            snapshotter.style.uri = mapView.mapboxMap.style.uri
            let cameraOptions = CameraOptions(cameraState: mapView.cameraState)
            snapshotter.setCamera(to: cameraOptions)
            DispatchQueue.main.async {
                print("Snapshotting...")
                snapshotter.start(overlayHandler: nil, completion: { result in
                    print("Result is \(result)")
                    switch result {
                    case .success(let image):
                        let snapshotImage = image
                        print("Snapshot yay")
                    case .failure(let error):
                        print("Snapshot generation failed with error: \(error)")
                    }
                })
            }

help me, mine's still not working

jsmmth commented 7 months ago

@haikallf incase you're still struggling with this issue. In Swift, local variables are deallocated once the scope in which they are defined is exited, if there are no strong references to them. This could mean that your snapshotter is being deallocated before it can complete the snapshot.

You should keep a strong reference to the snapshotter for the duration of its operation. You can do this by defining a property in your class class to hold the snapshotter. For example:

var snapshotter: Snapshotter?

private func snapshotMap() {
    self.snapshotter = Snapshotter(options: options)
    self.snapshotter?.style.uri = mapView.mapboxMap.style.uri
    let cameraOptions = CameraOptions(cameraState: mapView.cameraState)
    self.snapshotter?.setCamera(to: cameraOptions)

    DispatchQueue.main.async {
        print("Snapshotting...")
        self.snapshotter?.start(overlayHandler: nil) { [weak self] (result) in
            print("Result is \(result)")
            switch result {
            case .success(let image):
                let snapshotImage = image
                print("Snapshot yay")
            case .failure(let error):
                print("Snapshot generation failed with error: \(error)")
            }
        }
    }
}

Try that out and see if that helps :)