5

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?

E-Riddie
  • 14,660
  • 7
  • 52
  • 74
  • Hi you got any solution?? i wanted to implement lazy loading on marker's images but images are not shown. i am trying to do with AsyncImageview class.please tell me – Nisha Sep 05 '14 at 09:16
  • @Nisha You cannot do lazy loading with it. You need to download all the images async, and when downloading is completed, add the images for the pins. – E-Riddie Sep 05 '14 at 10:29
  • okey thanks for reply. Now it is solved using Grand Central Dispatch – Nisha Sep 05 '14 at 10:34
  • @Nisha Yes you can download them async, but how do you keep of track which image is being downloaded? – E-Riddie Sep 05 '14 at 10:47
  • right now i do not need to keep track.. i have used dispatch_async(myQueue, ^{}); method in for loop may be i can get using objectAtIndex:i not sure – Nisha Sep 05 '14 at 11:16
  • @Nisha If you have one pin this is really easy. But when you have a lot of pins which require images thats the point. You need to know if all downloads have finished, and them load them all. How do you manage this? Try to simulate it with bad connection on iOS Simulator and give conclusions! – E-Riddie Sep 05 '14 at 12:34
  • i do not need to wait for all downloads.. markers are loaded one by one.. [check this video](http://screencast.com/t/zIhp5cmPA6) – Nisha Sep 05 '14 at 13:01
  • @Nisha Aha I see your point now! Nice way of adding it, but when you have bad connection image loading will be slow, and the pins will wait for them to be shown. I do show all pins (default image) and after all images are download I load them all. Anyway this depends on your app requirements – E-Riddie Sep 05 '14 at 13:09
  • okey that is good pins will not have to wait using your technique. – Nisha Sep 05 '14 at 13:17

4 Answers4

6

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
hoangtuanfithou
  • 121
  • 1
  • 5
2

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

E-Riddie
  • 14,660
  • 7
  • 52
  • 74
0

maybe try this...move a couple lines inside the completion block, use marker...

[imageView setImageWithURL:[NSURL URLWithString:placeObject.photoPath] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType)
{
    image = [self image:image ByScalingAndCroppingForSize:CGSizeMake(44, 44)];
    [marker setIcon:image];
    [pins addObject:marker]; //assuming you use pins later
    marker.map = mapView_;
}];

since you already have the marker you're working on. of course, you will need to pay attention to your error handler.

gro
  • 755
  • 4
  • 7
  • I tried this before, no luck either! I don't have errors, but its just that marker is ignoring the image. – E-Riddie Jun 12 '14 at 11:31
  • verify your cacheType, then. – gro Jun 12 '14 at 11:44
  • I am not having problems with cache neither errors, I just need how to load image in the marker correctly – E-Riddie Jun 12 '14 at 11:50
  • You said this earlier 'If I reload data from the web and I go to this method again for adding pins, the image are loaded on markers.' Is this not the case? – gro Jun 12 '14 at 12:08
  • Yes but I want to load images on markers without having to reload. There is the possibility that images are being downloaded, and when I press refresh, the images are already in cache! – E-Riddie Jun 12 '14 at 12:11
  • try a dispatch_sync block – gro Jun 12 '14 at 12:18
  • I can use it, but thats the same as using SDWebImage – E-Riddie Jun 12 '14 at 12:20
  • I believe SDWebImage is an async/cache lib. You want to wait for the image, right? – gro Jun 12 '14 at 12:21
  • can't have it both ways...well, at least in this scenario. If you know the images you want early enough, make a call to get them cached before you get to the add markers method. – gro Jun 12 '14 at 12:23
  • How to make a solution which tells me that all images are downloaded. This can solve my problem! – E-Riddie Jun 12 '14 at 12:26
  • Maybe a counter in success handler to match pin count? or, condition being met on final pin image request success handler (eg. last url matched) – gro Jun 12 '14 at 12:28
  • Yes but if the last image has the smallest size, and gets downloaded faster than the other ones? That what's making me with doubts. Plus I cant assign a counter on completionHandler which is not on its thread. – E-Riddie Jun 12 '14 at 14:16
  • dude, it looks like they have some methods you should be able to leverage... progress/completed callbacks, start/stop downloads, etc. https://github.com/rs/SDWebImage/blob/master/SDWebImage/SDWebImageDownloader.m – gro Jun 12 '14 at 16:05
  • You are not getting it, the callback is when the image is completed so I know when one image is downloaded but my problem is how to count them. I am on background thread and I cant assign its counter thats the case – E-Riddie Jun 12 '14 at 17:01
0

same issue. And this is my solution: add 2 times

This is my code :D

- (void)addMarkers {
    //for() {
         NSString *avatarLink = [tempLocation.userInfo objectForKey:@"avatar"];
        GMSMarker *marker = [self addMarker:tempLocation andCustomView:customView];

        [customView.imageGame setImageWithURL:[NSURL URLWithString:avatarLink] placeholderImage:[UIImage imageNamed:LOADING_ICON] options:SDWebImageCacheMemoryOnly completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {
            marker.icon = [self captureView:customView];
        }];
    //}
}
- (GMSMarker*)addMarker: (FriendLocation*)tempLocation andCustomView: (FriendCustomInforwindow*)customView {
    GMSMarker *marker = [[GMSMarker alloc] init];
    marker.position = CLLocationCoordinate2DMake(tempLocation.coordinate.latitude, tempLocation.coordinate.longitude);
    marker.animated=YES;
    marker.infoWindowAnchor = CGPointMake(0.5, 2.37);
    marker.icon = [self captureView:customView];
    marker.map = mapView;
    [markersArray addObject:marker];
    return marker;
}