-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[ios, macos] possibility to set custom images to style #6637
Conversation
@rmnblm, thanks for your PR! By analyzing the history of the files in this pull request, we identified @1ec5, @frederoni and @incanus to be potential reviewers. |
MGLTypes.h declares |
Methods beginning with “add” typically append an object to an ordered collection, as in the case of A |
Good point about the "add" in front of methods and I totally agree with out but then again, it's inconsistent with the void addImage(const std::string&, std::unique_ptr<const SpriteImage>);
void removeImage(const std::string&); while the implementation of impl->style->spriteAtlas->setSprite(name, std::move(image));
I can do this but in my opinion this would be misleading. Wouldn't it be better to also follow the API convention of a NSMutableDictionary removeObjectForKey: - (void)removeImageForName:(NSString *)name; |
I just pushed commit 762f0a39574606f29d670f688ed5fba6c7bee83a with the following changes:
Usage: let image = UIImage(named: "animal-dog")!
mapView.style().setImage(image, forName: "dog")
let styleLayer = MGLSymbolStyleLayer(identifier: "restaurants", source: geoJSONSource)
styleLayer.predicate = NSPredicate(format: "amenity == 'restaurant'")
styleLayer.iconImage = "dog" as MGLStyleAttributeValue!
mapView.style().add(styleLayer) |
@@ -258,6 +258,21 @@ static const NSInteger MGLStyleDefaultVersion = 9; | |||
*/ | |||
- (void)removeStyleClass:(NSString *)styleClass; | |||
|
|||
/** | |||
Sets an image to the sprite atlas with a unique identifier. |
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.
“Sprite atlas” is jargon that most developers would find unintuitive. It’s probably sufficient to describe the image being added to the style (since it lasts until the style URL changes), then mention that the developer can use the image by setting the MGLSymbolStyleLayer.iconImage
property to the name passed in here.
/** | ||
Sets an image to the sprite atlas with a unique identifier. | ||
|
||
@param 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.
Need to document what image
is.
@@ -254,5 +257,21 @@ - (void)removeStyleClass:(NSString *)styleClass | |||
} | |||
} | |||
|
|||
- (void)setImage:(MGLImage *)image forName:(NSString *)name | |||
{ | |||
if (image && name) |
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.
Assert instead of failing gracefully. These two parameters are implied to be nonnull
in the header.
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.
You're right, I did not see that. Would it be sufficient to remove the if-Statement?
As a side note: Isn't this if-Statement also unnecessary?
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.
By default, passing in nil
for a nonnull
parameter is only a warning rather than an error, so NSAssert()
is for the benefit of developers who ignore their warnings. (Tsk, tsk!) On the other hand, we haven’t been disciplined about asserting in similar cases – -addStyleClass:
and -removeStyleClass:
are more examples of this – so it’s a bit unfair of me to nitpick about this detail. 😊
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'm glad you're taking it seriously and don't merge everything without some code review and I'm thankful for your nitpicking because I can only benefit from it.
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.
By the way: Do I have to NSAssert()
now inside both methods because (MGLImage *)image
and (NSString *)name
can't be null anyway and as a side quesstion: What is your recommended way to assert for null?
NSAssert([name isEqual:[NSNull null]], "Name cannot be null");
or
NSAssert(name == NULL, "Name cannot be null");
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.
The first form will crash unless name
is [NSNull null]
specifically. Since name
is of type NSString *
rather than id
, there is no way to avoid the crash.
The second form will crash unless name
is NULL
, which is the opposite of what you’d do in this case. Moreover, in Objective-C, nil
is conventionally used for Objective-C objects, NULL
is used for C pointers and double-pointers, and Nil
is used for Objective-C Class
es. I recommend reading this NSHipster article to learn about the various ways to say “nothing” in Objective-C.
In this case, you can simply NSAssert(name)
.
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.
Thanks for the article! If I can follow, you're expecting this:
NSAssert(image);
NSAssert(name);
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.
Yep.
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.
Just pushed it. I had to add a second argument with description otherwise I got error too few arguments provided to function-like macro invocation
.
|
||
- (void)removeImageForName:(NSString *)name | ||
{ | ||
if (name) |
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.
Similarly, assert here.
@@ -3,6 +3,14 @@ | |||
#pragma once | |||
|
|||
#if TARGET_OS_IPHONE | |||
@class UIImage; |
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.
👍
(Sorry, for some reason I thought we had already defined MGLImage, but in fact it was MGLColor that we implemented.)
@@ -5,6 +5,12 @@ | |||
#import <mbgl/util/default_styles.hpp> | |||
#include <mbgl/mbgl.hpp> | |||
|
|||
#if TARGET_OS_IPHONE |
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.
Can we move this over to MGLStyle.mm?
@@ -1,5 +1,7 @@ | |||
#import "MGLStyle.h" | |||
|
|||
#import "MGLTypes.h" |
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.
This should be unnecessary as long as MGLStyle.h imports MGLTypes.h.
@@ -7,6 +7,8 @@ | |||
objects = { | |||
|
|||
/* Begin PBXBuildFile section */ | |||
30E5781B1DAA857E0050F07E /* NSImage+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 30E578141DAA7D920050F07E /* NSImage+MGLAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; |
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.
This header’s visibility should be Project, not Public. Making it public would expose C++ syntax to pure Objective-C projects using the SDK.
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 first had UIImage+MGLAdditions
/NSImage+MGLAdditions
public but then realized it wasn't necessary, might have missed to set it back to Project here.
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.
Thanks for the revisions. You’re right about having a separate -removeImageForName:
, and putting the SpriteImage
conversion in a category is a great idea.
|
||
@interface UIImage (MGLAdditions) | ||
|
||
+ (std::unique_ptr<mbgl::SpriteImage>)mbgl_spriteImageFromImage:(UIImage *)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.
It would be more natural to have an instance method in this case: -mgl_spriteImage
. (Also, for consistency’s sake, the class prefix is mgl
rather than mbgl
.)
I just noticed that I wasn't consistent with my last two commit messages (delete/remove). 😑 |
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.
Almost there!
Sets an image to the sprite atlas with a unique identifier. | ||
Sets the name and image to the style. | ||
The image can be used by setting the MGLSymbolStyleLayer.iconImage | ||
property to the name passed in here |
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.
This method can be used to both add a new image or override an existing one, so we should make that clear. Also, when jazzy generates the HTML documentation, it automatically links symbols enclosed in backticks, to help readers navigate the multi-page documentation. This wording would be more verbose, but it leaves nothing open to interpretation:
Adds or overrides an image used by the style’s layers.
To use an image in a style layer, give it a unique name using this method, then set the
iconImage
property of anMGLSymbolStyleLayer
object to that name.
@@ -4,6 +4,6 @@ | |||
|
|||
@interface UIImage (MGLAdditions) | |||
|
|||
+ (std::unique_ptr<mbgl::SpriteImage>)mbgl_spriteImageFromImage:(UIImage *)image; | |||
+ (std::unique_ptr<mbgl::SpriteImage>)mgl_spriteImage:(UIImage *)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.
What I meant was that this would become a parameter-less instance method, like this:
- (std::unique_ptr<mbgl::SpriteImage>)mgl_spriteImage;
Within the implementation of an instance method, we have access to all the members of self
, such as self.CGImage
. Then you can simply say annotationImage.image.mgl_spriteImage
to get the unique_ptr
.
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.
Oops, now I know what you meant. Needed more coffee to understand it. ☕️
No worries. Normally we use GitHub’s “Squash and merge” feature, so the title of this PR will become the first line of the squashed commit message. If it bothers you, though, you can use |
It’s a bit unusual to have a setter and remover without an |
I also thought about a getter like |
Sorry, this ended up conflicting with #6636, which just got merged. Can you resolve the (minor) conflict or check the “Allow edits from maintainers” box so I push the resolution before merging? Thanks! |
I enabled edits from maintainers, the stage is yours 🏟 |
Thanks! 🎉 |
This commit to master failed to compile on CI:
|
Followup to last-minute refactoring in #6637.
Thanks @friedbunny. Bitrise CI doesn’t run on third-party PRs, unfortunately. Incoming fix in #6690. |
Followup to last-minute refactoring in #6637.
With the changes to the core in #6375 we can now add custom images to the sprite atlas. This PR resolves #6436 to add the possibility to add custom images from bundles in iOS and macOS.
As proposed from @1ec5 in #6436, I've first named the method
setImage:forName:
but to stay consistent with all other methods ofMGLStyle.h
I've decided to name the methodaddImage:forName:
.Usage:
Remarks:
This being my first PR and I'm relatively new to the codebase of Mapbox GL Native I hope I did everything correctly following the code and contribution guidelines.
During development I had to decide what is the argument type for the image being passed to
As we all know, to use images from a .xcassets folder, you can use
NSImage
on macOS orUIImage
on iOS but there is no base class for both to use as an argument type inThat's why I've decided to use a
NSString *
which is the absolute path of the image. This solution implies that you only can use images part of the bundle and not part of a .xcassets folder./cc @nitrag