Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More robust distance formatting #1176

Merged
merged 3 commits into from
Mar 2, 2018
Merged

More robust distance formatting #1176

merged 3 commits into from
Mar 2, 2018

Conversation

1ec5
Copy link
Contributor

@1ec5 1ec5 commented Mar 1, 2018

Implemented DistanceFormatter.attributedString(for:), which applies a quantity attribute to the numeric quantity part of the returned attributed string. This method relies on finding the formatted number within the distance string, as opposed to finding the unit, which turned out to be unreliable in some locales.

Simplified BaseInstructionsBannerView.distance’s setter to pass this attributed string directly to DistanceLabel instead of attempting to split the distance string on its quantity and unit. DistanceLabel now emphasizes the quantity relative to the unit based on the presence of the quantity attribute.

All this is to work around a Foundation bug in which LengthFormatter.unitString(fromValue:unit:) is unreliable in metricated Hebrew locales, such as Hebrew (Israel).

hebrew english
Left: The Hebrew (Israel) locale. Right: The English (United States) locale.

Original description:

Worked around a Foundation bug by splitting the distance string on whitespace if LengthFormatter.unitString(fromValue:unit:) doesn’t return the expected value. Now the distance to the upcoming maneuver appears in the turn banner, as expected, when the system language is set to Hebrew and the system region is set to a metricated region, such as Israel.

Fixes #1173.

/cc @JThramer @frederoni

@1ec5 1ec5 self-assigned this Mar 1, 2018
@1ec5 1ec5 requested review from frederoni and JThramer March 1, 2018 18:52
// Fall back on splitting the string on whitespace.
if unitRange == nil, let spaceRange = distanceString.rangeOfCharacter(from: .whitespaces) {
unitRange = spaceRange.upperBound..<distanceString.endIndex
distanceUnit = String(distanceString[unitRange!])
Copy link
Contributor

Choose a reason for hiding this comment

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

if let instead of force unwrapping.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

unitRange is set to a non-nil value in the line right above this line. if let isn’t suitable because unitRange is used outside the if statement. Specifically, if distanceString doesn’t contain the unit string and doesn’t contain a space, we still need to bail unless we come up with a different way to deemphasize the unit string. (Or should we just treat the entire string as the number?)

@bsudekum
Copy link
Contributor

bsudekum commented Mar 1, 2018

A lane test addition here would be 💯

@1ec5 1ec5 mentioned this pull request Mar 1, 2018
@1ec5
Copy link
Contributor Author

1ec5 commented Mar 1, 2018

A lane test addition here would be 💯

I think you’re thinking of #1175; this PR is unrelated to lanes.

I’ll look into extracting this logic into something unit-testable. 👍 Meanwhile, a snapshot test would be more trouble than it’s worth, because the fix would require a specific locale (he-IL). It is possible to change the formatter’s locale, but unfortunately that doesn’t affect the return value of LengthFormatter.unitString(forValue:unit:). We’d have to create a new scheme and Bitrise step just to test the fix.

@1ec5
Copy link
Contributor Author

1ec5 commented Mar 2, 2018

OK, I started over from scratch in ceb3c6e89ba545fe005c32532f3ff4113b85cabc: instead of looking for the unit within the distance string, look for the formatted number. This should be more reliable because we’re piggybacking on the distance formatter’s own number formatter. As a failsafe, if no quantity is found, the entire string is emphasized. In that worst-case scenario, it’s better to contort the layout a bit than to make the distance illegible.

DistanceFormatter is now responsible for creating the attributed string in the first place. It applies a custom attribute that makes it easier for DistanceLabel to find the quantity and apply presentational attributes to it. I could’ve simplified things further by having DistanceLabel invoke the distance formatter instead of BaseInstructionsBannerView, but I wasn’t sure if it was appropriate for a lightweight class in Style.swift to depend on something in Core Navigation.

The upshot is that DistanceFormatter.attributedString(for:) now has unit tests for multiple locales, all in the same scheme. 🎉

@1ec5 1ec5 changed the title Fix metric maneuver distances in Hebrew More robust distance formatting Mar 2, 2018
assertDistance(2_900, displayed: "२.९ कि॰मी॰", quantity: "२.९")
assertDistance(3_000, displayed: "३ कि॰मी॰", quantity: "३")
assertDistance(3_500, displayed: "४ कि॰मी॰", quantity: "४")
assertDistance(384_400_000, displayed: "३,८४,४०० कि॰मी॰", quantity: "३,८४,४००")
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 case you’re wondering, this is the distance to the Moon, and the first comma separates the lakhs place from the ten-thousands place.

@1ec5 1ec5 requested a review from bsudekum March 2, 2018 05:11
Worked around a Foundation bug by splitting the distance string on whitespace if LengthFormatter.unitString(fromValue:unit:) doesn’t return the expected value.
let attributedString = NSMutableAttributedString(string: string, attributes: attrs)
let convertedDistance = distance.converted(to: threshold(for: distance).unit)
if let quantityString = numberFormatter.string(from: convertedDistance as NSNumber) {
// NSMutableAttributedString methods accept NSRanges, not NSRanges.
Copy link
Contributor

Choose a reason for hiding this comment

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

NSRanges, not NSRanges 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, fixed.

1ec5 added 2 commits March 2, 2018 01:54
Implemented DistanceFormatter.attributedString(for:), which applies a “quantity” attribute to the numeric quantity part of the returned attributed string. This method relies on finding the formatted number within the distance string, as opposed to finding the unit, which turned out to be unreliable in some locales.

Simplified BaseInstructionsBannerView.distance’s setter to pass this attributed string directly to DistanceLabel instead of attempting to split the distance string on its quantity and unit. DistanceLabel now emphasizes the quantity relative to the unit based on the presence of the quantity attribute.

Added tests of attributed distance formatting.
If a quantity isn’t found within the distance string, emphasize the whole thing, for legibility’s sake. For the normal case where that failsafe isn’t applied, test that the unit isn’t emphasized.
@1ec5 1ec5 merged commit 047fea4 into master Mar 2, 2018
@1ec5 1ec5 deleted the 1ec5-rtl-distance-1173 branch March 2, 2018 10:10
@1ec5 1ec5 added this to the v0.15.0 milestone Mar 2, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants