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

[ios, macos] Add format expression convenience methods support. #14094

Merged
merged 10 commits into from
Mar 15, 2019

Conversation

fabian-guerra
Copy link
Contributor

Fixes #12739

I'm using MGLAttributedExpression as a custom structure that holds an expression and it's respective attributes. This structure is treated as a constant value but the underlaying expression property can be of any kind.

@fabian-guerra fabian-guerra added iOS Mapbox Maps SDK for iOS macOS Mapbox Maps SDK for macOS ⚠️ DO NOT MERGE Work in progress, proof of concept, or on hold needs changelog Indicates PR needs a changelog entry prior to merging. expressions labels Mar 12, 2019
@fabian-guerra fabian-guerra added this to the release-l milestone Mar 12, 2019
@fabian-guerra fabian-guerra self-assigned this Mar 12, 2019
@fabian-guerra fabian-guerra requested review from 1ec5, julianrex and a team March 12, 2019 23:20
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.

Suggested changelog entry:

  • Added the mgl_attributed: and MGL_ATTRIBUTED expression operators, which concatenate MGLAttributedExpression objects for specifying rich text in the MGLSymbolStyleLayer.text property.

Also, the “Predicates and Expressions” guide should be updated to reflect the addition of these new operators.

FOUNDATION_EXTERN MGL_EXPORT MGLAttributedExpressionKey const MGLFontSizeAttribute;

MGL_EXPORT
@interface MGLAttributedExpression : NSObject
Copy link
Contributor

Choose a reason for hiding this comment

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

This class seems to represent an attribute range (or attribute run) rather than an expression in its own right. Naming it MGLAttributedExpression could cause some confusion, since it looks like a subclass of NSExpression that can be set directly on NSExpression-typed properties. Acknowledging the difference with a name like MGLAttributedRun (akin to Core Text’s CTRun) would head off that confusion.

Ideally, we’d create a system akin to NSAttributedString, where you build up an NSAttributedExpression by applying attributes to ranges of the string and enumerate those ranges. But that’s potentially a large API surface area to implement, and it’s difficult to think in terms of ranges when the underlying content is of undefined length.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm using the same mental model between NSString and NSAttributedString where you can't assign the later to the first one and vice versa. I think MGLAttributedExpression keeps the concept of an object that contains an expression with attributes.

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 little different though: NSString doesn’t hold an array of NSAttributedStrings.

Copy link
Contributor

@julianrex julianrex Mar 14, 2019

Choose a reason for hiding this comment

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

I agree with @1ec5. How about something like MGLTextFormatAttributes? The MGLText prefix already exists as part of MGLSymbolStyleLayer...

@tobrun is there an equivalent to this in the Android SDK?

Copy link
Member

Choose a reason for hiding this comment

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

yes, we have:

textField(
         format(
           formatEntry("this is", formatFontScale(0.75)),
           formatEntry(
             concat(literal("\n"), get(TITLE_FEATURE_PROPERTY)),
             formatFontScale(1.25),
             formatTextFont(new String[] {"DIN Offc Pro Bold", "Arial Unicode MS Regular"})
           )
         )
       ),

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the concept that resembles the intent is MGLAttributedExpression, in regards to MGLTextFormatAttributes the underlaying object is not text per se (although it may contain a string constant).

@@ -1115,6 +1139,11 @@ - (id)mgl_jsonExpressionObject {
}
[NSException raise:NSInvalidArgumentException
format:@"Casting expression to %@ not yet implemented.", type];
} else if ([function isEqualToString:@"mgl_attributed:"] ||
[function isEqualToString:@"MGL_FORMAT:"] ||
[function isEqualToString:@"MGL_FORMAT"]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

MGL_FORMAT looks like something related to printf-style string formatting or number formatting (#13632) rather than rich text. Let’s stick to “attributed” terminology if possible.

Apart from that, MGL_FORMAT and MGL_FORMAT: need to be installed above in +installFunctions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I forgot to remove this lines I just wanted to use mgl_attributed: do you think it would be useful have MGL_ATTRIBUTED as well?

Copy link
Contributor

Choose a reason for hiding this comment

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

No, no need.

MGLAttributedExpression *attribute3 = [MGLAttributedExpression initWithExpression:[NSExpression expressionForConstantValue:@"bar"]
fontNames:nil
fontSize:@(0.8)];
MGLAttributedExpression *attribute4 = [MGLAttributedExpression initWithExpression:[NSExpression expressionForConstantValue:@"\r"]
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be possible to support the newline \n as well?

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 does support '\n' I will add a test for this.

MGLAttributedExpression *attribute4 = [MGLAttributedExpression initWithExpression:[NSExpression expressionForConstantValue:@"\r"]
fontNames:@[]
fontSize:nil];
NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_attributed(%@)", @[attribute1, attribute4, attribute2, attribute3]];
Copy link
Contributor

Choose a reason for hiding this comment

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

#12739 also envisioned a conversion from NSAttributedString to the format expression operator as a convenience. (A conversion to MGLAttributedExpression might make more sense, on second thought.) Note that NSAttributedString isn’t only intended for rich text; it’s meant to be a general-purpose container for strings that have attributes associated with portions of the text.

That doesn’t necessarily happen to happen as part of this PR, but I’d hold off on closing #12739 until there is something like that. The use case for an NSAttributedString conversion is a constant expression with sophisticated formatting that would be tedious to express as an NSExpression format string.

NSMutableDictionary *attrs = [NSMutableDictionary dictionary];

if (fontNames && fontNames.count > 0) {
[attrs setObject:fontNames forKey:MGLFontNamesAttribute];
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: use subscript notation:

Suggested change
[attrs setObject:fontNames forKey:MGLFontNamesAttribute];
attrs[MGLFontNamesAttribute] = fontNames;

(×2)

FOUNDATION_EXTERN MGL_EXPORT MGLAttributedExpressionKey const MGLFontSizeAttribute;

MGL_EXPORT
@interface MGLAttributedExpression : NSObject
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 class is public, remember to give it a documentation comment.

formatParameter = self.operand;
}

if ([formatParameter respondsToSelector:@selector(constantValue)] && [formatParameter.constantValue isKindOfClass:[NSArray class]]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

A developer might be tempted to create a constant-value NSExpression from a bare MGLAttributedExpression, which would be an error here. Maybe we could convert a standalone, constant MGLAttributedExpression into format expression too?

Copy link
Contributor

Choose a reason for hiding this comment

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

If we do that, then it starts to seem like we could combine this operator with mgl_join: so that joining MGLAttributedExpressions together creates a format expression instead of a concat expression.

@fabian-guerra fabian-guerra removed needs changelog Indicates PR needs a changelog entry prior to merging. ⚠️ DO NOT MERGE Work in progress, proof of concept, or on hold labels Mar 14, 2019
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.

Not thrilled about the naming of MGLAttributedExpression (see that thread), but that’s all I’m concerned about here. The implementation looks good.

<dd><code>mgl_attributed({x, y, z})</code></dd>
</dl>

Returns formatted text containing annotations for use in mixed-format `text-field` entries.
Copy link
Contributor

Choose a reason for hiding this comment

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

Concatenates and returns the array of MGLAttributedExpression objects, for use with the MGLSymbolStyleLayer.text property.

Returns formatted text containing annotations for use in mixed-format `text-field` entries.

This function corresponds to the
[`mgl_attributed`](https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-types-format)
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 called format in the style specification.

FOUNDATION_EXTERN MGL_EXPORT MGLAttributedExpressionKey const MGLFontSizeAttribute;

/**
An `NSExpression` that has associated text formatting attibutes (such as font size or
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn’t an NSExpression. It’s something that can be inserted into a specific operator inside an NSExpression.

@@ -403,6 +403,7 @@ In style specification | Method, function, or predicate type | Format string syn
`zoom` | `NSExpression.zoomLevelVariableExpression` | `$zoomLevel`
`heatmap-density` | `NSExpression.heatmapDensityVariableExpression` | `$heatmapDensity`
`line-progress` | `NSExpression.lineProgressVariableExpression` | `$lineProgress`
`format` | `mgl_attributed:` | `mgl_attributed({x, y, z})`
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 extend NSExpression with an initializer corresponding to this operator?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think makes sense.

@fabian-guerra fabian-guerra merged commit 7939ff2 into master Mar 15, 2019
@fabian-guerra fabian-guerra deleted the fabian-format-expression-12739 branch March 15, 2019 23:10
@julianrex
Copy link
Contributor

Thank you @fabian-guerra for getting this in 👍

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

Successfully merging this pull request may close these issues.

None yet

4 participants