-
Notifications
You must be signed in to change notification settings - Fork 2.2k
[ASMapNode] Add custom pin annotation for static maps #1890
[ASMapNode] Add custom pin annotation for static maps #1890
Conversation
Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla - and if you have received this in error or have any questions, please drop us a line at cla@fb.com. Thanks! |
@michalziman: thanks for contributing to the community by adding this functionality! I haven't reviewed your code yet, but with regards to the sample project, could you copy an existing ASDK sample project (cmd + D on any /example folder) and put the example project in that (e.g. top level folder can be named MapCustomAnnotationTest, but inner project needs to be named Sample and have same project settings. Thanks! |
@michalziman That is great that you added this, I was going to do that soon as well. @hannahmbanana my PR is in "development in progress" right now (#1792), it also includes a sample project for the ASMapNode, assuming my PR goes through, maybe these changes/samples could be merged into my PR (/its outcome) somehow? |
@hannahmbanana , @george-gw Thanks for commenting so quickly. :) Should I fix the sample project in my branch? Or maybe I can just wait till the other PR gets to master. Then update my branch merging wrong sample into the correct one from George. I am sorry I did not check other samples before to see if there is some common structure. |
@george-gw - thanks for commenting! I believe your PR is in good standing, but the reason it hasn't been merged yet is because few of us are very familiar with ASMapNode. @maicki - any reason we couldn't merge #1792 soon? @michalziman: thanks for the quick response! Assuming we can get @george-gw's PR merged soon, it would be nice to combine the two map sample projects into one. |
Hey @michalziman thanks for this! I've always wanted this feature in ASMapNode eventually. In terms of approach I'd prefer if you followed the current approach to customising ASMapNode, which is to have a public property on ASMapNode (with a sensible default). So for instance you would have:
The only downside to this would be that you can't have multiple different type of pins on the same map but the code would be a lot cleaner and more inline with the current approach, something to think about. |
Thanks @aaronschubert0 for an idea. As you mention, it would make the code cleaner. However, what I try to follow is making AsyncDisplayKit classes as similar in usage as possible to their UIKit (MapKit in this case) counterparts. MKMapView uses delegate, so it makes sense, that ASMapNode would do that, too. Using delegate to create views for annotations is not a difficult concept. And it also works with multiple kinds of custom pins. Which, is necessary almost every time when using custom pins. |
As a user of ASMapNode, I would like the map to have the same look regardless of whether it's a live map or an image. So the downside @aaronschubert0 mentioned would actually be a deal breaker. The path @michalziman took allows for that to happen without any additional code. However, I do see a potential problem with that approach: |
I was thinking about this issue with |
If we are to do what @aaronschubert0 suggested, I believe that property would have to be a block that would only be used when
Alternatively we could create an ASMapNodeDelegate that would add an extra method to handle that case. Thoughts? |
I like the block property approach a bit more. |
@michalziman @george-gw Thanks for this, that approach looks very clean! |
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks! |
|
||
for (id<MKAnnotation> annotation in annotations) { | ||
if (self.annotationViewInStaticMap) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please change self.
to strongSelf.
to be consistent with the rest of the function!
This is great stuff – the pin annotation limitation of ASMapNode has been a major oversight for a while. However, rather than returning a view, how about having the block return a UIImage? That way the client can use whatever approach they like – snapshotting a view, loading from disk, etc. Plus, since the snapshotter completion is off-main it's not safe to use UIViews in that context. The documentation for the block property should specify that the block will be called on an arbitrary serial queue. @michalziman Thoughts? |
Sure @Adlai-Holler , documentation should tell that block is called on an arbitrary queue. I am thinking about changing return type of the block. Returning view, it can carry location of pin's center. I can't think of simple way to bring this information in UIImage. And it is important for annotations. Also, with current approach, client can always use standard MKAnnotationView and just pass UIImage as .image property, just like in example. The drawback is necessity to render this view while snapshotting map. I am hesitating which approach is better. Any suggestion? |
Ah good point @michalziman. How about we mirror the MKAnnotationView
Just like the |
I modified the block as suggested. Probably the best approach, even though it seems a bit less straightforward to use. |
MKAnnotationView *av; | ||
if ([annotation isKindOfClass:[CustomMapAnnotation class]]) { | ||
av = [[MKAnnotationView alloc] init]; | ||
av.center = CGPointMake(21, 21); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we want to set center
here, since the map view is going to place the annotation wherever it decides. Was this meant to be centerOffset
?
- (UIImage *)defaultPinImageWithCenterOffset:(CGPoint *)centerOffset | ||
{ | ||
MKAnnotationView *pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""]; | ||
*centerOffset = pin.centerOffset; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of instantiating an MKPinAnnotationView every time (which can get very expensive!) let's make this a class method + (UIImage *)defaultPinImageWithCenterOffset:
and wrap the body of it in a dispatch_once (see my last comment for example code).
@michalziman Thanks for the updates! To fix the test failures – apologies for that – rebase this branch onto the latest master and let me know what you think of those comments. |
You should have used git rebase instead of merging master in, this way you don't get the "Merge branch 'master' of ..." message in the git history. Remove previous commit Fetch updates from original repo Rebase Push It seems you've done a merge at some point earlier as well, I'll leave that to the core ASDK team to decide the best course of action here. |
Why is not getting the "Merge branch 'master' of ..." message so important? Does it spoil something to just pull from upstream? What if there are some conflicts? It would be useful to see what was merged and when. I just can not imagine any good reason to rebase. |
pinImage = [strongSelf.class defaultPinImageWithCenterOffset:&pinCenterOffset]; | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this can be changed to:
Always get the default pin, just in case it might be used (better here than in a for loop)
CGPoint defaultPinCenterOffset = CGPointZero;
UIImage *defaultImage = [strongSelf.class defaultPinImageWithCenterOffset:&defaultPinCenterOffset];
Now that we have the default image, we can loop through the annotations, if the property is not set or if it returns nil, we will just use the default pin.
for (id<MKAnnotation> annotation in annotations) {
UIImage *pinImage = nil;
CGPoint pinCenterOffset = CGPointZero;
if (strongSelf.imageForStaticMapAnnotationBlock) {
pinImage = strongSelf.imageForStaticMapAnnotationBlock(annotation, &pinCenterOffset);
}
if (!pinImage) {
pinImage = defaultImage;
pinCenterOffset = defaultPinCenterOffset;
}
[...]
The idea is that if we are to use the defaultImage it is most likely going to be for more than one annotation, and this way we don't have to call the defaultPin several times since it will always return the same value anyway.
What do you think?
PS: If we won't go this way, either remove the comments that you have, or change the references from 'annotation view' to 'image for pin' or something to be consistent with the new code.
@michalziman with that kind of rebase you shouldn't get any merge conflicts. It creates a cleaner git log if you don't have all these merge messages, the history will be preserved in the correct order as well... In any case, perhaps someone from the core ASDK team can weigh in on this? @hannahmbanana maybe? (I'm not sure who the right person to @ for this case though hehe...) |
UIGraphicsEndImageContext(); | ||
return pinImage; | ||
if ([annotation isKindOfClass:[CustomMapAnnotation class]]) { | ||
MKAnnotationView *av = [grabbedSelf annotationViewForAnnotation:annotation]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm unfortunately we just cannot allocate views in here, since we're off the main thread. Since CustomMapAnnotation has an image property, let's use that. Also let's make sure to set centerOffset
to 21,21
After the rebase (thanks for it!) my comment is collapsed. I'm really excited to land this one so @michalziman if you get a sec to modify that would be awesome. |
73930a6
to
c7cd657
Compare
I am sorry guys for a little delay. I was on holiday. I did the rebase as you suggested. |
if (grabbedSelf) { | ||
if ([annotation isKindOfClass:[CustomMapAnnotation class]]) { | ||
MKAnnotationView *av = [grabbedSelf annotationViewForAnnotation:annotation]; | ||
return av.image; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't allocate views in this block, since we're off the main thread. @michalziman Since CustomMapAnnotation has an image property, can you modify this like:
CustomMapAnnotation *customAnnotation = (CustomMapAnnotation *)annotation;
return customAnnotation.image;
…creating annotation view.
Great! Thanks for your continued work bringing this all the way @michalziman! |
Thanks for helping, to all of you! |
* Remove background deallocation helper code Last use removed in Texture with facebookarchive#1840, now PINS no longer uses it either. Less OOMs is so nice. * remove methods from docs
* master_up: (43 commits) Do not expose tgmath.h to all clients of Texture (facebookarchive#1900) Call will / did display node for ASTextNode. Fixes facebookarchive#1680 (facebookarchive#1893) Remove background deallocation helper code (facebookarchive#1890) [Accessibility] Ship ASExperimentalDoNotCacheAccessibilityElements (facebookarchive#1888) 🎉 3.0.0 (facebookarchive#1883) Upgrade to Xcode 11.5 (facebookarchive#1877) Renames AS_EXTERN and ASViewController (facebookarchive#1876) Improve ThreeMigrationGuide.md (facebookarchive#1878) Add a 3.0 migration guide (facebookarchive#1875) I forgot this in the last PR and I'm pushing to master, I'm a bad person. Update for 3.0.0-rc.2 (facebookarchive#1874) Update RELEASE.md (facebookarchive#1873) Fix all the warnings and re-enable on CI (facebookarchive#1872) Prepare for 3.0.0-rc.1 release (facebookarchive#1870) -[ASNetworkImageNode setURL:resetToDefault:] forget to reset animatedImage (facebookarchive#1861) [ASDisplayNode] Implement accessibilityElementsHidden (facebookarchive#1859) Fix documentation for ASCornerRoundingTypeClipping (facebookarchive#1863) Add iOS13 UIContextMenu api to ASCommonCollectionDelegate (facebookarchive#1860) [ASDisplayNode] Implement accessibilityViewIsModal (facebookarchive#1858) Update FBSnapshotTestCase to iOSSnapshotTestCase (=6.2) (facebookarchive#1855) ...
Using delegate call for annotation view for annotation and drawing this view to static map image. This resolves #1889.
Also adds simple example. Just to demonstrate that it works.