PDA

View Full Version : [iOS] Black squares appearing after updating an SKAnnotation



michalzelinka
31.03.2016, 14:52
When updating the annotation appearance (switching image, changing background/border colours of views inside a customisable annotation view) black squares appear in some situations in place of annotations after calling -[SKMapView updateAnnotation:].

2462

How could we address this issue? We've discovered that simply updating our views inside annotation view do not update the appearance of marker at all, so we had to hook redundant delegates on them to be able to call updating method. But instead of updated view, only this thing happens quite frequently.

Adela_Silvia
05.04.2016, 17:18
Please try to fix the issue by setting the fillColor property of the circle to [UIColor clearColor]. If it's still not working please send us the steps to reproduce the issue and code snippets.

michalzelinka
06.04.2016, 07:39
Well, this is not a rect/circle nor any other SKOverlay, all of these are just SKAnnotations, which have no option to define any fill/stroke nor any other drawing stuff.

As you can see, we just simply take SKAnnoation subclass object, decide whether to perform insertion or just update, perform it. Annotation view consists of more layers, but that's pretty usual when customising their visual look.

The update call on SKMapView in -genericMarkerNeedsUpdate: after switching the inner image with photo often fails by presenting these black squares.

As you tend to do some internal magic with the annotation view which needs the explicit call to update (proved by experimenting with changing indication colours of our annotation views with no immediate effect), I suppose that's the part where the issue is – some caching or re-drawing stuff. Updating UIImage data of UIImageView is pretty atomic and expectable operation for map annotations so I can't find a line which should be punishing any flow. The raw code follows.

Annotation wrapper definition:


@interface MapAnnotationWrapper : SKAnnotation <MKAnnotation>

@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;
@property (nonatomic, assign) CLLocationCoordinate2D coordinate; // compat over location property
... few other properties

@end


MapView part:


@interface MapView : UIView
...
@property (nonatomic, strong) SKMapView *map;
...
@end

@implementation MapView
...
- (void)addAnnotation:(id)annotation
{
MapAnnotationWrapper *wrapper = annotation;

BOOL shouldInsert = NO;
SKAnnotation *anno = [_map annotationForIdentifier:wrapper.identifier];
if (!anno) {
anno = wrapper;
shouldInsert = YES;
}

UIView *view = [self mapView:_map layerForAnnotation:wrapper];

NSString *identifier = [NSString stringWithFormat:@"%zd", wrapper.identifier];
anno.annotationView = [[SKAnnotationView alloc] initWithView:view reuseIdentifier:identifier];
anno.identifier = wrapper.identifier;
anno.location = wrapper.coordinate;

if (shouldInsert) {
SKAnimationSettings *animation = [[SKAnimationSettings alloc] init];
animation.animationType = SKAnimationPopOut;
animation.duration = 300;
animation.animationEasingType = SKAnimationEaseLinear;
[_map addAnnotation:anno withAnimationSettings:animation];
} else
[_map updateAnnotation:anno];
}

- (UIView *)mapView:(SKMapView *)aMapView layerForAnnotation:(MapAnnotationWrapper *)annotation
{
UIView *marker = nil;

MapAnnotationWrapper *wrapper = (MapAnnotationWrapper *)annotation;

if (wrapper.type == kMapAnnotationTypeActivity)
{
ActivityRMMarker *a = [[ActivityRMMarker alloc] initWithAnnotationWrapper:wrapper];
a.delegate = self;
marker = a;
}

// ...other types are never updating

return marker;
}

- (void)genericMarkerNeedsUpdate:(GenericMarker *)marker
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if (marker && marker.annotation)
[_map updateAnnotation:marker.annotation];
}];
}
...
@end


Annotation views part:


@implementation GenericMarker : UIView

- (instancetype)initWithUIImage:(UIImage *)image
{
if (self = [super initWithFrame:CGRectMake(0, 0, image.size.width, image.size.height)])
{
UIImageView *img = [[UIImageView alloc] initWithImage:image];
[self.layer addSublayer:img.layer];
self.layer.masksToBounds = NO;
}

return self;
}

@end


@interface ActivityRMMarker ()

@property (nonatomic, strong) TripomaticImageView *photoView;
@property (nonatomic, strong) UIView *indicationView;

@end


@implementation ActivityRMMarker

- (instancetype)initWithAnnotationWrapper:(MapAnnota tionWrapper *)annotationWrapper
{
Activity *activity = annotationWrapper.data;

// Full-sized annotation with image and flag
//
// Detailed annotations consists of basic shape image (white circle with shadow)
// in the background and composed Tripomatic image which displays category image
// of the Activity and if possible fetches and displays a real photo of the Acti-
// vity. Setting is performed by -setActivityImage:, callback calls the delegate
// callback on MapView above to perform the update. Indication view provides up-
// datable circle around inner image to indicate In Trip / Today / Favourites
// colour coding on the Map.

if (annotationWrapper.showDetail) {

NSString *imageName = @"annotation-small";
if (activity.tier.intValue == 1 || activity.isPopular)
imageName = @"annotation-large";
else if (annotationWrapper.plannedState || annotationWrapper.favouritesState)
imageName = @"annotation-medium";

if (self = [super initWithUIImage:[UIImage imageNamed:imageName]])
{
CGRect frame = self.frame;

self.layer.shouldRasterize = YES;
self.layer.rasterizationScale = [UIScreen mainScreen].scale;

_photoView = [[TripomaticImageView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width-11, frame.size.width-11)];
_photoView.viewType = TripomaticImageViewTypeMap;
_photoView.layer.cornerRadius = _photoView.width/2.0;

_indicationView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width-6, frame.size.width-6)];
_indicationView.layer.cornerRadius = _indicationView.width/2.0;
_indicationView.layer.borderWidth = 3;

_photoView.layer.position = _indicationView.layer.position =
CGPointMake(frame.size.width/2.0, frame.size.height/2.0);

[self.layer addSublayer:_photoView.layer];
[self.layer addSublayer:_indicationView.layer];

__weak typeof (self) wself = self;

[_photoView setActivityImage:activity type:kImageTypeSmall completion:^(Medium *m) {
__strong typeof (self) sself = wself;
if (sself.photoView.image.size.width < 150) return;
if ([sself.delegate respondsToSelector:@selector(genericMarkerNeedsUpd ate:)])
[sself.delegate genericMarkerNeedsUpdate:sself];
}];
}
}

... // non-detailed marker is not updating

return self;
}

@end

Adela_Silvia
13.04.2016, 16:54
We've reported this ticket to our development team. Can you please let us know if you're using the 2.5.1 SDK?

michalzelinka
13.04.2016, 17:42
Yes, we certainly use latest 2.5.1 version. Firstly the one from official page, currently releasing with hotfix build pinned at the top of this forum.

dandronic
13.06.2016, 08:37
Via the dev team: it seems like updateAnnotation: is called inside a block, which means it can be executed on a different thread.
Please dispatch to the main thread before calling UI methods, this should solve the issue.

Code snippet:

- (void)genericMarkerNeedsUpdate:(GenericMarker *)marker
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if (marker && marker.annotation) {
dispatch_async(dispatch_get_main_queue(), ^{
[_map updateAnnotation:marker.annotation];
});
}
}];
}

michalzelinka
13.06.2016, 08:41
It IS called on a main thread, don't you see? The block is dispatched on main queue (NSOperationQueue mainQueue) which belongs to main thread; it cannot be fired elsewhere.

There's no sense to encapsulate it with another main queue dispatching, this is only a syntax sugar with a difference of using NSOperationQueue or GCD, but there's no point on dispatching something on main thread when the whole stuff is already dispatched on a main thread.

Adela_Silvia
11.08.2016, 14:15
hi Michal,
Our developer couldn't reproduce this in the 2.5.1 latest hotfix.
Have you checked the 3.0 build? Have you encounter this issue in 3.0 also?