-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Add convenience initializers to shape source #7665
Add convenience initializers to shape source #7665
Conversation
bb0446f
to
6b99f55
Compare
Do we actually need a |
@@ -56,6 +56,16 @@ - (instancetype)initWithIdentifier:(NSString *)identifier shape:(nullable MGLSha | |||
return self; | |||
} | |||
|
|||
- (instancetype)initWithIdentifier:(NSString *)identifier features:(NS_ARRAY_OF(MGLFeature) *)features options:(nullable NS_DICTIONARY_OF(MGLShapeSourceOption, id) *)options { | |||
MGLShapeCollectionFeature *shapeCollectionFeature = [MGLShapeCollectionFeature shapeCollectionWithShapes:features]; |
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.
Should we check the type of each object in features
and log a one time warning if a non-feature makes it through?
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.
Since lightweight generic qualifiers are completely optional in Objective-C, I think it’s fairly likely that a developer would incorrectly attempt to pass in an array of MGLShapes, especially if we get rid of -initWithIdentifier:shapes:options:
. I think we should raise an exception in that case, since the value would contradict the type expected by this method.
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.
In addition to the comments below, refer to -initWithIdentifier:features:options:
in the -initWithIdentifier:shape:options:
initializer as a way to put multiple features in the same source.
@@ -143,6 +142,41 @@ extern const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance; | |||
*/ | |||
- (instancetype)initWithIdentifier:(NSString *)identifier shape:(nullable MGLShape *)shape options:(nullable NS_DICTIONARY_OF(MGLShapeSourceOption, id) *)options NS_DESIGNATED_INITIALIZER; | |||
|
|||
/** | |||
Returns a shape source with an identifier, a shape that is a | |||
`MGLShapeCollectionFeature` holding the features passed in, and dictionary of |
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 takes an array of features, not a single shape collection feature object.
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 does take an array but that is not what it returns in the "shape" property of the instantiated object. Isn't that what this sentence is trying to explain?
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 sentence explains what you pass into the method.
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.
Ah of course! I think not seeing "initialized with" threw me off for some reason today :D
`MGLShapeCollectionFeature` holding the features passed in, and dictionary of | ||
options for the source. | ||
|
||
Passing an array of `MGLShape` subclass instances that conform to the |
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 is a given.
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 could be a @note
instead? My thinking is that we should take every chance we have to point out the subtle difference between shapes and features. If we do get rid of -initWithIdentifier:shapes:options:
then this note could probably go away completely.
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.
Unlike
-initWithIdentifier:shapes:options:
, this method acceptsMGLFeature
instances, such asMGLPointFeature
objects, whose attributes you can use when applying a predicate toMGLVectorStyleLayer
or configuring a style layer’s appearance.
|
||
/** | ||
Returns a shape source with an identifier, a shape that is a | ||
`MGLShapeCollection` holding the shapes passed in, and dictionary of |
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 takes an array of shapes, not a single shape collection object.
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.
Same as #7665 (comment)
`+[MGLShape shapeWithData:encoding:error:]` method. | ||
|
||
@param identifier A string that uniquely identifies the source. | ||
@param features An array of `MGLShape` subclass instances that conform to the `MGLFeature` protocol |
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.
An array of objects that conform to the
MGLFeature
protocol.
`+[MGLShape shapeWithData:encoding:error:]` method. | ||
|
||
@param identifier A string that uniquely identifies the source. | ||
@param shapes An array of `MGLShape` subclass instances |
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.
Missing a period, and “subclass instances” is a bit confusing:
An array of shapes; each shape is a member of a concrete subclass of
MGLShape
.
@@ -56,6 +56,16 @@ - (instancetype)initWithIdentifier:(NSString *)identifier shape:(nullable MGLSha | |||
return self; | |||
} | |||
|
|||
- (instancetype)initWithIdentifier:(NSString *)identifier features:(NS_ARRAY_OF(MGLFeature) *)features options:(nullable NS_DICTIONARY_OF(MGLShapeSourceOption, id) *)options { |
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.
NS_ARRAY_OF(MGLFeature) *
resolves to NSArray<MGLFeature> *
, meaning the passed-in value must be an NSArray object that conforms to the MGLFeature protocol. I think what you meant was NS_ARRAY_OF(id <MGLFeature>) *
.
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.
Correct. The header is written that way but it did not make it here somehow.
@@ -56,6 +56,16 @@ - (instancetype)initWithIdentifier:(NSString *)identifier shape:(nullable MGLSha | |||
return self; | |||
} | |||
|
|||
- (instancetype)initWithIdentifier:(NSString *)identifier features:(NS_ARRAY_OF(MGLFeature) *)features options:(nullable NS_DICTIONARY_OF(MGLShapeSourceOption, id) *)options { | |||
MGLShapeCollectionFeature *shapeCollectionFeature = [MGLShapeCollectionFeature shapeCollectionWithShapes:features]; |
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.
Since lightweight generic qualifiers are completely optional in Objective-C, I think it’s fairly likely that a developer would incorrectly attempt to pass in an array of MGLShapes, especially if we get rid of -initWithIdentifier:shapes:options:
. I think we should raise an exception in that case, since the value would contradict the type expected by this method.
MGLShapeCollectionFeature *shape = (MGLShapeCollectionFeature *)source.shape; | ||
|
||
XCTAssertTrue([shape isKindOfClass:[MGLShapeCollectionFeature class]]); | ||
XCTAssert(shape.shapes.count == 1, @"Shape collection should contain 1 shape"); |
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.
Use XCTAssertEqual()
(×2).
Also, can you see if the “Working with GeoJSON Data” guide needs any updates? |
Returns a shape source with an identifier, a shape that is a | ||
`MGLShapeCollection` holding the shapes passed in, and dictionary of | ||
options for the source. | ||
|
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.
Any
MGLFeature
instance passed into this initializer is treated as an ordinary shape, causing any attributes to be inaccessible to anMGLVectorStyleLayer
when evaluating a predicate or configuring certain layout or paint attributes. To preserve the attributes associated with each feature, use the-initWithIdentifier:features:options:
or-initWithIdentifier:shape:options:
method instead.
@@ -143,6 +142,41 @@ extern const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance; | |||
*/ |
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 documentation for this method currently says:
To include multiple shapes in the source, use an
MGLShapeCollection
orMGLShapeCollectionFeature
object.
Change it to:
To include multiple shapes in the source, use an
MGLShapeCollection
orMGLShapeCollectionFeature
object, or use the-initWithIdentifier:features:options:
or-initWithIdentifierr:shapes:options:
method.
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 was done in 6ba0034
3a6f2f4
to
6ba0034
Compare
Updated the guide in 6ba0034 |
@@ -36,6 +36,17 @@ resulting shape or feature object into the | |||
the map, or you can use the object and its properties to power non-map-related | |||
functionality in your application. | |||
|
|||
To include multiple shapes in the source, create and pass an `MGLShapeCollection` or | |||
`MGLShapeCollectionFeature` object to | |||
`-[MGLShapeSource initWithIdentifier:shape:options:]`. Or, use the |
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.
s/Or/Alternatively/
`MGLShapeCollectionFeature` object to | ||
`-[MGLShapeSource initWithIdentifier:shape:options:]`. Or, use the | ||
`-[MGLShapeSource initWithIdentifier:features:options:]` or | ||
`-[MGLShapeSource initWithIdentifier:shapes:options:]` methods to create shape |
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.
method to create a shape source with an array
[NSException raise:NSInvalidArgumentException format:@"The object %@ included in the features argument does not conform to the MGLFeature protocol.", feature]; | ||
} | ||
} | ||
MGLShapeCollectionFeature *shapeCollectionFeature = [MGLShapeCollectionFeature shapeCollectionWithShapes:(NSArray *)features]; |
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 likely had to cast here because +[MGLShapeCollectionFeature shapeCollectionWithShapes:]
accepts an array of MGLShape <MGLFeature> *
instead of an array of id <MGLFeature>
. Let’s make this method and +[MGLShapeCollectionFeature shapeCollectionWithShapes:]
consistent with each other. (I have no strong preference one way or the other, but id <MGLFeature>
might leave more options open for fixing #7454.)
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.
Since MGLShapeCollectionFeature
inherits from MGLShapeCollection
and MGLShapeCollection
uses an NS_ARRAY_OF(MGLShape *)
I don't think I can change MGLShapeCollectionFeature
since it would require the change to bubble up to MGLShapeCollection
😬
The implementation of MGLShapeCollectionFeature
does not use lightweight generics (just a plain old NSArray). I made the same change to MGLShapeSource
in 6287c6b. Are you ok with this solution for this case, for now?
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 implementation of +[MGLShapeCollectionFeature shapeCollectionWithShapes:]
uses an unqualified NSArray
in its signature, but the declaration has a qualified NSArray<MGLShape<MGLFeature> *> *
. Omitting the qualifier in the implementation only takes away opportunities for the compiler to catch mistakes. We should qualify as much as possible.
Since +[MGLShapeCollectionFeature shapeCollectionWithShapes:]
is declared to take an NSArray<MGLShape<MGLFeature> *> *
rather than the broader type NSArray<id<MGLFeature>> *
, -[MGLShapeSource initWithIdentifier:features:options:]
should take an NSArray<MGLShape<MGLFeature> *> *
as well.
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.
Got it. This is done in e85da7f. I'll merge after the build passes.
This adds two new convenience initializers to MGLShapeSource: -initWithIdentifier:features:options: takes an array of shape objects that conform to MGLFeature, inserts them in a shape collection feature and creates a source with that shape. -initWithIdentifier:shapes:options does the same but with concrete MGLShape objects that get added to a shape collection. Throw an exception if an shape source is created with the features initializer but is sent an array of features that contains something that is not actually an object that conforms to the feature protocol. Updates to geojson data guide Qualify APIs that take arrays of shapes that are features
e85da7f
to
4166635
Compare
Fixes #7453
This adds two new convenience initializers to MGLShapeSource:
-initWithIdentifier:features:options:
takes an array of shape objects that conform to MGLFeature, inserts them in a shape collection feature, and creates a source with that shape.-initWithIdentifier:shapes:options:
does the same but with concrete MGLShape objects that get added to a shape collection.Although both are technically convenience initializers, developers will most often want to use -
initWithIdentifier:features:options:
since features objects unlock key runtime styling functionality. So, the inline example has been updated to demonstrateMGLShapeSource
usage with-initWithIdentifier:features:options:
The type system will help ensure that Swift developers use the correct convenience initializer for objects that are features (i.e. it is not possible to send plain shapes to the features initializer without an unsafe cast). There is currently no warning system for this though so Objective-C developers will not be protected from bugs where layer properties or predicate filters don't work as expected because plain shapes were previously used with the features initializer.
cc @frederoni @bsudekum @ericrwolfe