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

Add back MGLStyleFunction and re-document MGLStyleValue #7943

Merged
merged 13 commits into from
Feb 8, 2017

Conversation

boundsj
Copy link
Contributor

@boundsj boundsj commented Feb 4, 2017

Fixes: #7934

Addresses MGLStyleValue documentation todos noted in #7924:

  • Update class documentation for MGLStyleValue (as a class cluster it represents something different now)
  • +[MGLStyleValue valueWithInterpolationMode:cameraStops:options:]
  • +[MGLStyleValue valueWithInterpolationMode:sourceStops:attributeName:options:]
  • +[MGLStyleValue valueWithInterpolationMode:compositeStops:attributeName:options:]
  • MGLCameraStyleFunction (and all methods and properties)
  • MGLSourceStyleFunction (and all methods and properties)
  • MGLCompositeStyleFunction (and all methods and properties)

@boundsj boundsj added documentation iOS Mapbox Maps SDK for iOS macOS Mapbox Maps SDK for macOS refactor runtime styling labels Feb 4, 2017
@boundsj boundsj added this to the ios-v3.5.0 milestone Feb 4, 2017
@boundsj boundsj self-assigned this Feb 4, 2017
@boundsj boundsj requested a review from 1ec5 February 4, 2017 01:06
@mention-bot
Copy link

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

@boundsj
Copy link
Contributor Author

boundsj commented Feb 4, 2017

This PR adds back MGLStyleFunction so that style functions can be considered more abstractly in code and documentation.

A constraint here is that we don't want to break backwards compatibility with 3.4.x's MGLStyleFunction and code that may have expected not only to reference the class but also its interpolationBase and stops properties. Concurrently, we want MGLStyleFunction to be a base class for all of the concrete function classes and not a kind of wrapper for MGLCameraStyleFunction specifically.

To do this I:

  • Removed -@compatibility_alias MGLStyleFunction MGLCameraStyleFunction
  • Added back a MGLStyleFunction but documented it as an abstract base class that is not to be instantiated directly or subclassed
  • Added interpolationBase and stops properties to MGLStyleFunction so it is fully backwards compatible

The main tension point was the stops property combined with lightwieght generics: Since MGLStyleFunction needs stops to be backwards compatible but not all of the concrete function subclasses share the same generic annotation types, I intentionally added a plain NSDictionary stops property to the abstract MGLStyleFunction with covariants in each of the subclasses with the fully expressed, lightweight generic annotations. I think this allows for full backwards compatibility, stays out of the way when dealing with the subclasses, and still allows for (in Swift) iterating over functions, cast in the abstract, and casting stops even for the most complex (composite) case:

// circleStyleLayer.circleRadius  previously set to a `MGLCompositeStyleFunction`
if let returnedCircleRadius = circleStyleLayer.circleRadius as? MGLStyleFunction<NSNumber> {
  if let returnedStops = returnedCircleRadius.stops as NSDictionary? as? [NSNumber: [NSNumber: MGLStyleValue<NSNumber>]] {
    let lhs: MGLStyleValue<NSNumber> = returnedStops[0]!.values.first!
    let rhs: MGLStyleValue<NSNumber> = radiusCompositeExponentialOrIntervalStops[0]!.values.first!
                XCTAssertEqual(lhs, rhs)
   }
}

@return An `MGLCompositeStyleFunction` object with the given interpolation mode,
composite stops, attribute name, and options.
*/
+ (instancetype)valueWithInterpolationMode:(MGLInterpolationMode)interpolationMode compositeStops:(NS_DICTIONARY_OF(id, NS_DICTIONARY_OF(id, MGLStyleValue<T> *) *) *)compositeStops attributeName:(NSString *)attributeName options:(nullable NS_DICTIONARY_OF(MGLStyleFunctionOption, id) *)options;
Copy link
Contributor

Choose a reason for hiding this comment

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

If each stop is “interpreted as a source function”, should the type of compositeStops be NSDictionary<id, NSDictionary<id, MGLSourceStyleFunction<T> *> *> *?

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 was me doing a poor job of trying to clarify the opposite: "composite" in this case means a composite of stops dictionaries not functions (see discussion in #7596 (comment)). In d46559a, I punted on trying to explain this in the class factory method in favor of the explanation in the MGLCompositeStyleFunction stops property

// TODO: API docs
/**
The exponential interpolation base of the function’s interpolation curve.
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

Document the fact that interpolationBase may not apply to all style functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

// TODO: API docs
/**
The exponential interpolation base of the function’s interpolation curve.
*/
@property (nonatomic) CGFloat interpolationBase;

@end
Copy link
Contributor

Choose a reason for hiding this comment

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

For backwards compatibility, MGLStyleFunction also needs the initializers that it had in v3.4.0. These initializers can be marked as deprecated.

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 will probably look a bit strange no matter what we do. I think 406e647 is a workable solution until a semver bump when we can more officially make this class abstract.

The initializers have been brought back and an actual instance of MGLStyleFunction is created and returned if you use them. The conversion methods have been updated to coerce MGLStyleFunction instances into MGLCameraStyleFunction instances with the appropriate interpolation mode. However, documentation attempts to point developers to the MGLStyleValue class factory methods to avoid all of this.

An `MGLCameraStyleFunction` is a value function defining a style value that changes
as the zoom level changes. The layout and paint attribute properties of an
`MGLStyleLayer` object can be set to `MGLCameraStyleFunction` objects. Use a zoom
level function to create the illusion of depth and control data density.
Copy link
Contributor

Choose a reason for hiding this comment

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

“Zoom level functions” are now known as “camera functions”.

@1ec5
Copy link
Contributor

1ec5 commented Feb 5, 2017

I intentionally added a plain NSDictionary stops property to the abstract MGLStyleFunction with covariants in each of the subclasses with the fully expressed, lightweight generic annotations. I think this allows for full backwards compatibility, stays out of the way when dealing with the subclasses, and still allows for (in Swift) iterating over functions, cast in the abstract, and casting stops even for the most complex (composite) case

Note that this approach technically doesn’t guarantee full backwards compatibility with Swift code:

let circleRadius = circleStyleLayer.circleRadius as! MGLStyleFunction<NSNumber>
circleStyleLayer.circleRadius = circleRadius.stops[14]

must now be converted to at least something like this:

let circleRadius = circleStyleLayer.circleRadius as! MGLStyleFunction<NSNumber>
circleStyleLayer.circleRadius = (circleRadius.stops as! [NSNumber: MGLStyleFunction<NSNumber>])[14]

This isn’t a problem in Objective-C, where you can use NSDictionary * as a more specific type without casting. We’ve carved out an exception to semantic versioning for Swift bridging changes, although it’s a last resort.

@return An `MGLCameraStyleFunction` object with the given interpolation mode,
camera stops, and options.
*/
+ (instancetype)valueWithInterpolationMode:(MGLInterpolationMode)interpolationMode cameraStops:(NS_DICTIONARY_OF(id, MGLStyleValue<T> *) *)cameraStops options:(nullable NS_DICTIONARY_OF(MGLStyleFunctionOption, id) *)options;
Copy link
Contributor

Choose a reason for hiding this comment

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

Remember to use the NS_…_OF macros until we drop iOS 8 support.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It looks like we forgot to do this in the 3.4.0 release. In fbb8068 and 3b90d85 I updated the deprecated methods and everything in the resurrected MGLStyleFunction to use the macro. The only use of NSDictionary (with no lightweight generic attributes) in MGLStyleValue now is for the stops property of MGLStyleFunction for reasons already discussed in this PR.

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.

MGLStyleFunction is going to be a bit messy, sort of like CLRegion (versus CLCircularRegion), for awhile, but I think we have the start of a workable solution.

/**
The modes used to interpolate property values between map zoom level changes or
over a range of feature attribute values.
*/
@property (nonatomic) MGLInterpolationMode interpolationMode;
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we avoid effectively adding an interpolationMode property to MGLStyleFunction that would only sometimes be applicable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In ee89e94 I removed it in MGLStyleFunction and added it back to the concrete subclasses. This sort of makes sense because an MGLStyleFunction technically does not (did not) have an interpolation mode. In the future, we should add the property back to MGLStyleFunction because every actual function class does have an interpolation mode.

Copy link
Contributor

Choose a reason for hiding this comment

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

every actual function class does have an interpolation mode

Ah, I misunderstood. In that case, it’s fine (and preferable) to have it in MGLStyleFunction to avoid duplication.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah. For now, it'll be a bit of a lie, but still reasonable.

MGLStyleFunction's interpolation mode will be exponential even though it did not have such a concept before in 3.4.x. However, any MGLStyleFunction instance that is put to use in 3.5.0 is treated like a camera function with an exponential interpolation mode so I feel ok about that. I'll put it back on MGLStyleFunction.

@param stops A dictionary associating zoom levels with style values.
@return An `MGLStyleFunction` object with the given interpolation base and stops.
*/
- (instancetype)initWithInterpolationBase:(CGFloat)interpolationBase stops:(NS_DICTIONARY_OF(NSNumber *, MGLStyleValue<T> *) *)stops NS_DESIGNATED_INITIALIZER;
Copy link
Contributor

Choose a reason for hiding this comment

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

If you mark this as a designated initializer, does that mean all the subclass initializers have to call this method? Or would removing NS_DESIGNATED_INITIALIZER be considered a breaking change?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ideally, in a future release, we’d end up making all -init… methods private (as would be expected of class clusters). So we should make all the new -init… methods (like in MGLCameraStyleFunction) private and leave this one public but deprecated, for backwards compatibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you mark this as a designated initializer, does that mean all the subclass initializers have to call this method?

Yes

Or would removing NS_DESIGNATED_INITIALIZER be considered a breaking change?

I don't think so. We never suggested that developers should subclass MGLStyleFunction.

In 7cbeba7, I exposed only MGLStyleFunction's initializer publicly but
deprecated it and removed its designated initializer status. Internally this allows us to ignore the deprecated initializer in subclasses.

}

+ (instancetype)functionWithInterpolationBase:(CGFloat)interpolationBase stops:(NSDictionary *)stops {
return [[self alloc] initWithInterpolationBase:interpolationBase stops:stops];
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we initialize an MGLCameraStyleFunction here instead (and in +functionWithStops:, but not -init or -initWithInterpolationBase:stops:)? If there are warnings associated with the designated initializer, we can suppress the warnings.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done 2f414f2

}

- (NSUInteger)hash {
return self.stops.hash + self.interpolationBase;
Copy link
Contributor

Choose a reason for hiding this comment

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

Use @(self.interpolationBase).hash, not interpolationBase itself, to further minimize collisions. There are similar problems with interpolationMode in the subclasses. Feel free to take care of this in a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done 1b67069

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.

Sorry for flip-flopping on interpolationMode; based on your explanation in #7943 (comment), it does make sense to declare it on MGLStyleFunction and not each of its subclasses.

Otherwise, this PR looks good to go.

@boundsj boundsj merged commit 06a8ed7 into master Feb 8, 2017
@boundsj boundsj deleted the boundsj-add-function-base-class branch February 8, 2017 20:27
@nitrag
Copy link
Contributor

nitrag commented Apr 26, 2017

I'm super lost.

let carLayer = MGLSymbolStyleLayer(identifier: "cars-layer", source: carSource)
style.setImage(UIImage(named: "car-red")!, forName: "car-red")
style.setImage(UIImage(named: "car-blue")!, forName: "car-blue")
style.setImage(UIImage(named: "car-silver")!, forName: "car-silver")
style.setImage(UIImage(named: "car-green")!, forName: "car-green")
style.setImage(UIImage(named: "car-orange")!, forName: "car-orange")
carLayer.iconImageName = MGLSourceStyleFunction(
    interpolationMode: .identity,
    sourceStops: nil,
    attributeName: "car-color",
    options: [.defaultValue: MGLStyleValue<NSString>(rawValue: "car-silver")]
)
carLayer.iconImageName = MGLCameraStyleFunction(
    interpolationMode: .identity,
    sourceStops: nil,
    attributeName: "car-color",
    options: [.defaultValue: MGLStyleValue<NSString>(rawValue: "car-silver")]
)

regardless of which one I choose:
'NSInvalidArgumentException', reason: 'This property can only be set to camera functions. Use +[MGLStyleValue valueWithInterpolationMode:cameraStops:options:] instead.'

but I need attributeName to tell it what field? No??

@jfirebaugh
Copy link
Contributor

Data-driven styling for iconImageName is not in any released iOS SDK version yet. It'll be in 3.6.0.

If you're ever in doubt about SDK compatibility, https://www.mapbox.com/mapbox-gl-js/style-spec/ has support matrixes for every property (albeit you'll need to translate from SDK property name to style spec property name).

@nitrag
Copy link
Contributor

nitrag commented Apr 26, 2017

Ahhhhhh. I'll remember this for the future. Thanks!

@1ec5
Copy link
Contributor

1ec5 commented Apr 26, 2017

The documentation for MGLSymbolStyleLayer.iconImageName in v3.5.2 says it supports camera functions but not source functions. We use the same automated process to generate the property’s implementation, documentation, and the exception message you ran into, so you can be sure of its accuracy.

albeit you'll need to translate from SDK property name to style spec property name

The iOS SDK’s documentation comes with a full correspondence table.

@wr6524798
Copy link

please, when I use Data-driven styling for iconImageName,the Application crash, I want use Data-driven styling for iconImageName,when the 3.6.0. release?

@1ec5
Copy link
Contributor

1ec5 commented Apr 27, 2017

@wr6524798, this closed pull request isn’t the best place for new bug reports. Please open a new issue about the crash you’re seeing, and be sure to include the crash trace so we can properly diagnose and prioritize the problem. Thank you!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
documentation iOS Mapbox Maps SDK for iOS macOS Mapbox Maps SDK for macOS refactor runtime styling
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants