Search code examples
swiftuisnapshotgoogle-maps-sdk-ios

Take Google Maps' snapshot and send to Photo Album in SwiftUI


I'm trying to capture a Google Maps snapshot in SwiftUI, but seems I can't figure out what I'm not getting, although my guess is that I'm almost there.

When I click the Camera FAB, my app must create a png file on my device's album. The problem is: the picture is a white square.

Here's the whole code:

import Foundation
import SwiftUI
import GoogleMaps

struct HomeScreenshot: View {
    var btnTake : some View { Button(action: {

        // HERE'S WHERE A KNOW I MUST DO SOMETHING, BUT JUST CAN'T FIGURE OUT WHAT! :-(
        // I'VE FOUND ON STACKOVERFLOW THE LOGIC BELOW, BUT IT'S NOT WORKING AS I NEED


        // THEORETICALLY, THIS SHOULD CAPTURE THE WHOLE SCREEN TO AN UIVIEW
        let someView = UIView(frame: UIScreen.main.bounds)

        // AND CONVERT INTO AN UIIMAGE
        let imagem = someView.takeScreenshot()

        // AND THEN GENERATE THE PNG FILE ON DEVICE'S DOCUMENTS DIRECTORY
        if let data = imagem.pngData() {

            let filename = Utilitarios().getDocumentsDirectory().appendingPathComponent("image_file.png")

            try? data.write(to: filename)

            print("\(filename) successfully writen but in a figure of a white square")

            // THIS IS NOT WORKING. THE PNG FILE GENERATED IS A WHITE SQUARE ON IOS PHOTOS
        }
    }) {
            Image(systemName: "camera.fill")
                .resizable()
                .frame(width: AppDelegate.tamanhoIconePequeno, height: AppDelegate.tamanhoIconePequeno)
                .foregroundColor(Color.white)
            }
            .frame(width: AppDelegate.tamanhoFAB, height: AppDelegate.tamanhoFAB )
            .background(Color.orange)
            .cornerRadius(38.5)
            .padding(.bottom, 20)
            .padding(.trailing, 10)
            .shadow(color: Color.black.opacity(0.3),
                    radius: 3,
                    x: 3,
                    y: 3)
    }

    var body: some View {
        ZStack() {
            GoogleMapsHomeScreenshot().edgesIgnoringSafeArea(.all)
            VStack{
                Spacer()
                HStack{
                    Spacer()
                    btnTake
                }
            }.padding()
        }
        .navigationBarBackButtonHidden(true)
        .navigationBarTitle(Text("Snapshot test"), displayMode: .inline)
    }
}

struct HomeScreenshot_Previews: PreviewProvider {
    static var previews: some View {
        HomeScreenshot()
    }
}

Here's the GoogleMaps definition:

struct GoogleMapsHomeScreenshot: UIViewRepresentable {
    private let zoom: Float = 18

    // arbitrary coordinates, just for testing purposes
    let lat: Double = -15.6692660716233
    let lng: Double = -47.83980712156295

    func makeCoordinator() -> Coordinator {
        return Coordinator(
            owner: self
        )
    }

    class Coordinator: NSObject, GMSMapViewDelegate, ObservableObject {
        let owner: GoogleMapsHomeScreenshot
        init( owner: GoogleMapsHomeScreenshot ) {
            self.owner = owner
        }         
    }

    func makeUIView(context: Self.Context) -> GMSMapView {
        let camera = GMSCameraPosition.camera(
            withLatitude: lat,
            longitude: lng,
            zoom: zoom)
        let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
        mapView.mapType = .hybrid
        mapView.delegate = context.coordinator              
        return mapView
    }

    func updateUIView(_ mapView: GMSMapView, context: Context) {    
    }
}

And finally, this extends UIView to take the snapshot (here's where I think my problem is).

extension UIView {
    func takeScreenshot() -> UIImage {

        // Begin context
        UIGraphicsBeginImageContextWithOptions(UIScreen.main.bounds.size, false, UIScreen.main.scale)

        // Draw view in that context
        drawHierarchy(in: UIScreen.main.bounds, afterScreenUpdates: true)

        // And finally, get image
        let image = UIGraphicsGetImageFromCurrentImageContext()

        UIGraphicsEndImageContext()

        if (image != nil) {
            UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil);
            return image!
        }
        return UIImage()
    }
}

How can I do that?


Solution

  • Here is my implementation. I hope it will help you.

    struct ContentView: View {
        let map = GMSMapView.map(withFrame: CGRect.zero, camera: GMSCameraPosition.camera(withLatitude: 16.8409, longitude: 96.1735, zoom: 6.0)) // This is GMSMapView object to share its copy to MapView.
        var body: some View {
            ZStack(alignment: .bottomTrailing) {
                MapView(mapView: map)
                Button(action: {
                    self.saveToLibrary() // It save the capture of the current position of map
                }) {
                    Circle()
                        .frame(width: 50, height: 50)
                }
                .padding()
                .padding(.bottom)
            }.edgesIgnoringSafeArea(.all)
        }
        func saveToLibrary() {
            let image = map.layer.makeSnapshot() // It captures a snapshot of current view
            guard let selectedImage = image else {
                return
            }
            UIImageWriteToSavedPhotosAlbum(selectedImage, nil, nil, nil)
        }
    }
    
    struct MapView: UIViewRepresentable {
        var mapView: GMSMapView
        func makeUIView(context: Self.Context) -> GMSMapView {
            return mapView
        }
    
        func updateUIView(_ mapView: GMSMapView, context: Self.Context) { }
    }
    
    extension CALayer {
        func makeSnapshot() -> UIImage? {
            let scale = UIScreen.main.scale
            UIGraphicsBeginImageContextWithOptions(frame.size, false, scale)
            defer { UIGraphicsEndImageContext() }
            guard let context = UIGraphicsGetCurrentContext() else { return nil }
            render(in: context)
            let screenshot = UIGraphicsGetImageFromCurrentImageContext()
            return screenshot
        }
    }