Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Per-attribute transition properties on MGLStyleLayer #8225

Merged

Conversation

fabian-guerra
Copy link
Contributor

Fixes #8010

@fabian-guerra fabian-guerra added iOS Mapbox Maps SDK for iOS runtime styling labels Feb 28, 2017
@fabian-guerra fabian-guerra added this to the ios-v3.5.0 milestone Feb 28, 2017
@fabian-guerra fabian-guerra self-assigned this Feb 28, 2017
@mention-bot
Copy link

@fabian-guerra, thanks for your PR! By analyzing this pull request, we identified @1ec5, @boundsj and @incanus to be potential reviewers.

@incanus incanus changed the title Per-attribute transition properties on MGLStyleLaye Per-attribute transition properties on MGLStyleLayer Feb 28, 2017
Copy link
Contributor

@incanus incanus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this PR be based against the 3.5.0 release branch since it builds upon data-driven styling?

@@ -85,6 +85,21 @@ typedef NS_OPTIONS(NSUInteger, MGLMapDebugMaskOptions) {
#endif
};

/**
A structure containing information about a transition values.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove values.

NSTimeInterval duration;

/**
The delay in seconds to before applying any changes to the style URL or to layout and paint attributes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove to in to before.

{
NSString *camelCaseKey;
if ([key length] > 1) {
camelCaseKey = [NSString stringWithFormat:@"%@%@",[[key substringToIndex:1] uppercaseString],[key substringFromIndex:1]];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code style: spaces after ,.

@@ -14,6 +14,8 @@
#import "MGLStyleValue_Private.h"
#import "MGL<%- camelize(type) %>StyleLayer.h"

#import "NSDate+MGLAdditions.h"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put this further up with the others, next to #import "NSPredicate+MGLAdditions.h", a similar category use.

@@ -30,6 +30,7 @@ NS_ASSUME_NONNULL_BEGIN
*/
static MGL_EXPORT const NSInteger MGLStyleDefaultVersion = 9;


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extraneous whitespace.

@@ -194,6 +195,11 @@ MGL_EXPORT
@property (nonatomic, strong) NS_SET_OF(__kindof MGLSource *) *sources;

/**
Transition values.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should describe it a bit more here, maybe:

Values describing animated transitions to styling changes, either to the style URL or to individual properties.

- (void)setTransition:(MGLTransition)transition
{
[self setTransitionDuration:transition.duration];
[self setTransitionDelay:transition.delay];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably get rid of the separate internal -setTransitionDuration: and -setTransitionDelay: since externally we only set full transition structures containing both values.


[setPropertyInvocation setArgument:&transition atIndex:2];

[setPropertyInvocation performSelector:@selector(invoke)];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't this be simply -[NSObject performSelector:withObject:] since it's only one argument, passing the transition wrapped in NSValue?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@incanus using an NSValue I will have to wrap it then unwrap in set***Transition:, also I will have to add pragma marks to avoid the "PerformSelect may cause a leak because...".

I think this approach is more straightforward, what are your thoughts?

Copy link
Contributor

@boundsj boundsj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you continue with this PR, please also add appropriate tests (generated with MGLStyleLayerTests.mm.ejs) and the obligatory changelog entry 😄

NSString *setPropertyTransitionString = [NSString stringWithFormat:@"mbx_set%@Transition:", camelCaseKey];
SEL setPropertyTransitionSelector = NSSelectorFromString(setPropertyTransitionString);

if ([self respondsToSelector:setPropertyTransitionSelector]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the main motivation to introduce dynamic method invocation for transitions?

Most of the complexity here is about parsing the string and forming the private method name to match the actual implementation and that introduces new types of edge cases that we did not previously need to handle. For example, what if the client app passes in a nonsensical string at runtime? So far, I think this implementation would fail silently. Should it log a warning, raise an exception, etc.? Do we expect to offer a string parameter based getter for each transition, too?

Since the style layer implementation is generated and in the spirit of consistency with the existing API and its per property getters and setters, I suggest that we consider making the mbx_ methods in this implementation public and avoid the dynamic invocation altogether.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was pointed to the original post in #8010 (comment) so this makes sense now. Thanks!

I still vote for explicit getters and setters. However, if we do go with the CALayer influenced API, it would be nice to try a refactor so that some or all of this string parsing and method invocation logic goes into the base MGLStyleLayer class.

@@ -1204,6 +1206,15 @@ MGL_EXPORT
* `MGLCameraStyleFunction` with an interpolation mode of:
* `MGLInterpolationModeExponential`
* `MGLInterpolationModeInterval`
* `MGLSourceStyleFunction` with an interpolation mode of:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a byproduct of the style spec / gl native mismatch. All of these comments should be manually removed in this PR (or work against a style spec gitsha that does not cause this to happen to avoid the inconvenience)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it is likely that #7939 will land before this PR and, I think, this problem will go away then once this branch is rebased on top of that change set.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, if you rebase this PR on top of master as of now, f901e77 will fix this problem 👍

@boundsj
Copy link
Contributor

boundsj commented Feb 28, 2017

@incanus Should this PR be based against the 3.5.0 release branch since it builds upon data-driven styling?

The 3.5.0 branch is actually currently master. We expect to make (and share with Android) a 3.5.0 release branch later today or tomorrow. We can point this PR to that branch once it exists.

@fabian-guerra fabian-guerra force-pushed the fabian-per-attribute-transition branch 2 times, most recently from bbdb9f4 to 27b6f8d Compare March 2, 2017 22:34
- (NSArray *)transitionKeys
{
// This is overridden by subclasses
return nil;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Raise an MGLAbstractClassException. Search for that string for an example to follow.

[transitionKeys addObject:@"backgroundOpacity"];
[transitionKeys addObject:@"backgroundPattern"];

return transitionKeys;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be more efficient to return an array literal.

@@ -84,13 +85,33 @@ - (void)removeFromMapView:(MGLMapView *)mapView

#pragma mark - Accessing the Paint Attributes

- (NSArray *)transitionKeys
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this method doesn’t have any dependencies on other instance methods, you can make this a class method. But I’m unclear on this method’s purpose. It appears to be unused.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method returns the keys which you can add a transition

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, looking more closely, this method appears to be used only in tests (but is exposed publicly). As an alternative to this method, could we move the array of keys into the tests themselves?

@1ec5
Copy link
Contributor

1ec5 commented Mar 4, 2017

Since this PR is scheduled for iOS SDK v3.5.0, please rebase and retarget it at the release-ios-v3.5.0-android-v5.0.0 branch. Thanks!

@fabian-guerra fabian-guerra force-pushed the fabian-per-attribute-transition branch from 27b6f8d to f0adb64 Compare March 6, 2017 19:40
@fabian-guerra fabian-guerra changed the base branch from master to release-ios-v3.5.0-android-v5.0.0 March 6, 2017 21:16
Copy link
Contributor

@incanus incanus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also seeing extraneous commits like 33f1982 in the mix. Is this because you've brought over some changes from master as well @fabian-guerra?


return transitionKeys;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, per https://github.com/mapbox/mapbox-gl-native/pull/8225/files#r104082076, you could do like this:

+ (NSArray *)transitionKeys
{    
    return @[
<% for ...
       @"<%- camelizeWithLeadingLowercase(property.name) %>",
<% } -%>
    ];
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@incanus I was using master but retarget to rebase release-ios-v3.5.0-android-v5.0.0

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, do git rebase -i newBranchName and remove things you don't want as part of the PR. Then force-push and finally retarget the PR.

@fabian-guerra fabian-guerra force-pushed the fabian-per-attribute-transition branch from ba516bc to 95c452e Compare March 7, 2017 01:00
@@ -43,7 +43,7 @@ public static synchronized Mapbox getInstance(@NonNull Context context, @NonNull
return INSTANCE;
}

private Mapbox(@NonNull Context context, @NonNull String accessToken) {
Mapbox(@NonNull Context context, @NonNull String accessToken) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR seems to have picked up additional, unrelated commits along the way when it was rebased like this one from #8228

@incanus incanus force-pushed the fabian-per-attribute-transition branch from 95c452e to 7cedbff Compare March 7, 2017 01:10
@incanus
Copy link
Contributor

incanus commented Mar 7, 2017

Fixed #8225 (review) manually with a rebase dropping these oddball non-@fabian-guerra commits and force-pushed to fix 👍

@fabian-guerra fabian-guerra force-pushed the fabian-per-attribute-transition branch 2 times, most recently from a324ae8 to 2e51b1e Compare March 7, 2017 01:24
Copy link
Contributor

@1ec5 1ec5 left a 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, please add an entry to the iOS and macOS changelogs about this enhancement. Thanks!

/**
Returns an array of strings that identify transition keys attached to the layer.
*/
+ (NSArray *)transitionKeys;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we expose this method publicly? If so, let's call it transitionableAttributes and also make the return type a set of strings instead of an unqualified array.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Method is intended to explicitly return which layer attributes are transitionable, we can as well add a note on each property, but I think this is a better way to let know devs about what they can change, also this can be used as helper to set the same transition property to all transitionable attributes

@@ -69,6 +69,17 @@ MGL_EXPORT
*/
@property (nonatomic, assign) float minimumZoomLevel;

#pragma mark Layer Transitions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jazzy uses these marks to produce section headings in the generated documentation. For consistency with Apple's documentation, we label each mark according to a task. Here, we can say "Timing Changes to Attributes".

@@ -69,6 +69,17 @@ MGL_EXPORT
*/
@property (nonatomic, assign) float minimumZoomLevel;

#pragma mark Layer Transitions

- (void)setTransition:(MGLTransition)transition forKey:(NSString *)key;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Public methods need documentation comments.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking we might want to call it -setTransition:forAttribute: to be more specific about the kind of key that's being transitioned.

return transition;
}

- (SEL)mbx_selectorForKey:(NSString *)key type:(NSString *)type hasParams:(BOOL)params
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need to prefix private methods with a class prefix the way we prefix category methods.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method would be a bit simpler if we had two methods: one for getters and one for setters.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since all the valid setter method names form a closed set, we should generate a lookup table mapping attributes to the corresponding setter name, then look up the passed-in attribute in that table to decide what to call. That way, we can raise an exception when the developer passes in an untransitionable attribute or a bogus attribute. Currently, doing that leads to a nasty crash with no useful information.

Copy link
Contributor

@incanus incanus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm now not positive that the getter use of -Warc-performSelector-leaks is safe, since we are creating a new NSValue inside of mbx_getFooTransition and we aren't telling ARC how to dispose of it (the use in setters is fine since they return void). So I think the overridden leak warning is a valid one.

i.e. What happens to the NSValue at https://github.com/mapbox/mapbox-gl-native/pull/8225/files#diff-24c0d8cf4ae8bed8e0bc00616e9aae68R89 after we return the wrapped transition?

Additionally, per NSValue docs:

This method has the same effect as valueWithBytes:objCType: and may be deprecated in a future release. You should use valueWithBytes:objCType: instead.

@fabian-guerra fabian-guerra force-pushed the fabian-per-attribute-transition branch from 2e51b1e to 1ffc2e6 Compare March 8, 2017 01:46
@fabian-guerra
Copy link
Contributor Author

After talking to @1ec5 and @boundsj I realized that the trade-off for having a single method to set transition VS a straightforward approach was not enough to justify the added complexity.

Each transitionable style attribute has it's corresponding method.

@@ -69,6 +69,11 @@ MGL_EXPORT
#endif

/**
`backgroundColor` transition attributes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The transition affecting any changes to this layer’s backgroundColor property.

This property corresponds to the background-color-transition property in the style JSON file format.

(We can't quite say "in the Mapbox Style Specification", as we do elsewhere, because of mapbox/mapbox-gl-js#4081.)

@@ -59,6 +62,11 @@ - (void)testProperties {
XCTAssertThrowsSpecificNamed(layer.backgroundColor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it");
functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil];
XCTAssertThrowsSpecificNamed(layer.backgroundColor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it");
// Transition property test
layer.backgroundColorTransition = transitionTest;
MGLTransition backgroundColorTransition = layer.backgroundColorTransition;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By setting and getting before asserting, we're verifying that the transition value round-trips. That's good, but it doesn't rule out the possibility that both the getter and setter are buggy. To rule that out, we'd need to assert after setting that the underlying mbgl::style::BackgroundLayer::getBackgroundColorTransition() is correct.

@@ -1753,6 +1814,7 @@ MGL_EXPORT
*/
@property (nonatomic, null_resettable) MGLStyleValue<NSValue *> *textTranslationAnchor;


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: stray line.

*/
NSTimeInterval delay;
} MGLTransition;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should provide a factory method MGLTransitionMake(). We might also want to add transition-related methods to NSValue(MGLAdditions) to make it easier to work with MGLTransition instances when an object is needed.

*/
typedef struct MGLTransition {
/**
The duration in seconds to animate any changes to the style URL or to layout and paint attributes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit of a nonsequitur to talk about style URLs and attributes here. This structure is generic enough that its documentation should be more self-contained. For example:

The amount of time the animation should take, not including the delay.

(It doesn't include the delay, does it?)

NSTimeInterval duration;

/**
The delay in seconds before applying any changes to the style URL or to layout and paint attributes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The amount of time in seconds to wait before beginning the animation.

@fabian-guerra fabian-guerra force-pushed the fabian-per-attribute-transition branch from d85657b to 77dea62 Compare March 9, 2017 18:51
Copy link
Contributor

@1ec5 1ec5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost there!

@@ -69,6 +69,13 @@ MGL_EXPORT
#endif

/**
The transition affecting any changes to this layer’s `backgroundColor` property.

This property corresponds to the background-color-transition property in the style JSON file format.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surround background-color-transition in backticks.

options.delay = MGLTimeIntervalFromDuration(toptions.delay.value_or(mbgl::Duration::zero()));
options.duration = MGLTimeIntervalFromDuration(toptions.duration.value_or(mbgl::Duration::zero()));
XCTAssertEqual(options.delay, transitionTest.delay);
XCTAssertEqual(options.duration, transitionTest.duration);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would structure this a little differently:

  • Set fillColorTransition to transitionTest.
  • Call getFillColorTransition().
  • Assert toptions.delay && *toptions.delay == transitionTest.delay.


/**
Creates a new `MGLTransition` from the given duration and delay.
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document each @param and the @return value.

@@ -70,6 +71,22 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (readonly) MGLOfflinePackProgress MGLOfflinePackProgressValue;

#pragma mark Working with Transition values
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: s/values/Values/

@fabian-guerra fabian-guerra force-pushed the fabian-per-attribute-transition branch from 77dea62 to a63be28 Compare March 10, 2017 01:12
Copy link
Contributor

@1ec5 1ec5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great.


@return Returns a `MGLTransition` struct containing the transition attributes.
*/
NS_INLINE MGLTransition MGLTransitionMake(NSTimeInterval duration, NSTimeInterval delay) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely sure, but you may also need to MGL_EXPORT this function. Generally we have to export public, top-level symbols, but this one is inlined. @kkaefer would know for sure.

@boundsj boundsj dismissed their stale review March 10, 2017 06:37

Looks good to me!

@fabian-guerra fabian-guerra force-pushed the fabian-per-attribute-transition branch from a63be28 to 09f6f9f Compare March 10, 2017 17:30
[self getValue:&transition];
return transition;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why have these utilities anymore? We don't use them.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been pointed at #8225 (comment). 👍🏻

@fabian-guerra fabian-guerra merged commit 20712b7 into release-ios-v3.5.0-android-v5.0.0 Mar 10, 2017
@incanus
Copy link
Contributor

incanus commented Mar 10, 2017

🎉

@incanus incanus deleted the fabian-per-attribute-transition branch March 10, 2017 21:17
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
iOS Mapbox Maps SDK for iOS runtime styling
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants