Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GMSMarker icon download via URL

Setting an icon to GMSMarker in Google Maps SDK requires the UIImage, but currently my requirements are downloading it from a specific URL

Issue

The problem is that somehow only the last item sometimes is being shown. This the code on how I create markers (Updated in Swift)

func createMarkers() {
    mapView.clear()

    let mapCoordinates: [CLLocationCoordinate2D] = coordinates()
    let iconURLs: [URL] = imageURLs()

    var marker = GMSMarker()
    for i in 0..<mapCoordinates.count {
        let imageURL = iconURLs[i]

        marker = GMSMarker()
        marker.position = mapCoordinates[i]
        marker.map = mapView

        downloadImage(from: imageURL) { image in
            marker.icon = image
        }
    }
}

// It is a helper function calling `SDWebImage` which caches the `UIImage` based on its `URL` string
func downloadImage(from url: URL, completion: @escaping (UIImage) -> Void)

From code provided above, I am having trouble while I am loading the data first time, because pins are showing on map but without image. If I call createMarkers() again after some time, the icons are loaded correctly.

I don't know why this is happening, any suggestion or hint to make fix this issue?

like image 274
E-Riddie Avatar asked Jun 12 '14 11:06

E-Riddie


2 Answers

Just use SDWebimage, and .iconView not .icon

let imageView = UIImageView(image: pinImage)
imageView.sd_setImage(with: URL(string: "https://pbs.twimg.com/profile_images/469017630796296193/R-bEN4UP.png"), placeholderImage: pinImage)
marker.iconView = imageView
like image 167
hoangtuanfithou Avatar answered Oct 11 '22 11:10

hoangtuanfithou


Okay back then, I did not know much about concurrency, and seeing all the answers did not actually solved what I really had in mind.

Issue

Since the downloading an image from the URL (downloadImage(from url: URL, completion: @escaping (UIImage) -> Void)) is a background operation, by the time that the image has been downloaded, marker has gone out of scope, by being initialized again on the loop.

This way there is no way to know which image was being bound to which marker.

Also regarding Why it works when reloading it?, the SDWebImage when being reloaded has already download and cached the image, so the next time there is no delay, it's just bounded to the marker directly.

Solution

Move the mutable variable markers as an immutable value inside the loop.

func createMarkers() {
    let mapCoordinates: [CLLocationCoordinate2D] = coordinates()
    let iconURLs: [URL] = imageURLs()

    for i in 0..<mapCoordinates.count {
        let imageURL = iconURLs[i]

        let marker = GMSMarker()
        marker.position = mapCoordinates[i]
        marker.map = mapView
        applyImage(from: imageURL, to: marker)
    }
}

Now you see I introduced a helper called applyImage(from:, to:) which basically does nothing special but downloads the image from the URL and binds it to the argument of type GMSMarker.

func applyImage(from url: URL, to marker: GMSMarker) {
    DispatchQueue.global(qos: .background).async {
        guard let data = try? Data(contentsOf: url),
            let image = UIImage(data: data)?.cropped()
            else { return }

        DispatchQueue.main.async {
            marker.icon = image
        }
    }
}

Extra there is an extension for cropping the image to 44pt X 44pt, this is not right, but it's just to preview what has been loaded.

extension UIImage {

    func cropped() -> UIImage? {
        let cropRect = CGRect(x: 0, y: 0, width: 44 * scale, height: 44 * scale)

        guard let croppedCGImage = cgImage?.cropping(to: cropRect) else { return nil }

        return UIImage(cgImage: croppedCGImage, scale: scale, orientation: imageOrientation)
    }
}

NOTE: The whole ViewController code can be found this Gist I wrote!

Output

Demo

like image 22
E-Riddie Avatar answered Oct 11 '22 10:10

E-Riddie