summaryrefslogtreecommitdiff
path: root/platform/ios/src/MGLScaleBar.mm
diff options
context:
space:
mode:
Diffstat (limited to 'platform/ios/src/MGLScaleBar.mm')
-rw-r--r--platform/ios/src/MGLScaleBar.mm221
1 files changed, 164 insertions, 57 deletions
diff --git a/platform/ios/src/MGLScaleBar.mm b/platform/ios/src/MGLScaleBar.mm
index 8525881da7..3efa80013f 100644
--- a/platform/ios/src/MGLScaleBar.mm
+++ b/platform/ios/src/MGLScaleBar.mm
@@ -82,16 +82,20 @@ static const MGLRow MGLImperialTable[] ={
@property (nonatomic, assign) MGLRow row;
@property (nonatomic) UIColor *primaryColor;
@property (nonatomic) UIColor *secondaryColor;
-@property (nonatomic) CALayer *borderLayer;
@property (nonatomic, assign) CGFloat borderWidth;
@property (nonatomic) NSMutableDictionary* labelImageCache;
@property (nonatomic) MGLScaleBarLabel* prototypeLabel;
@property (nonatomic) CGFloat lastLabelWidth;
-
+@property (nonatomic) CGSize size;
+@property (nonatomic) BOOL recalculateSize;
+@property (nonatomic) BOOL shouldLayoutBars;
+@property (nonatomic) NSNumber *testingRightToLeftOverride;
@end
static const CGFloat MGLBarHeight = 4;
static const CGFloat MGLFeetPerMeter = 3.28084;
+static const CGFloat MGLScaleBarLabelWidthHint = 30.0;
+static const CGFloat MGLScaleBarMinimumBarWidth = 30.0; // Arbitrary
@interface MGLScaleBarLabel : UILabel
@@ -137,6 +141,8 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
}
- (void)commonInit {
+ _size = CGSizeZero;
+
_primaryColor = [UIColor colorWithRed:18.0/255.0 green:45.0/255.0 blue:17.0/255.0 alpha:1];
_secondaryColor = [UIColor colorWithRed:247.0/255.0 green:247.0/255.0 blue:247.0/255.0 alpha:1];
_borderWidth = 1.0f;
@@ -144,17 +150,17 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
self.clipsToBounds = NO;
self.hidden = YES;
- _containerView = [[UIView alloc] init];
- _containerView.clipsToBounds = YES;
- _containerView.backgroundColor = self.secondaryColor;
+ _containerView = [[UIView alloc] init];
+ _containerView.clipsToBounds = YES;
+ _containerView.backgroundColor = _secondaryColor;
+ _containerView.layer.borderColor = _primaryColor.CGColor;
+ _containerView.layer.borderWidth = _borderWidth / [[UIScreen mainScreen] scale];
+
+ _containerView.layer.cornerRadius = MGLBarHeight / 2.0;
+ _containerView.layer.masksToBounds = YES;
+
[self addSubview:_containerView];
- _borderLayer = [CAShapeLayer layer];
- _borderLayer.borderColor = [self.primaryColor CGColor];
- _borderLayer.borderWidth = 1.0f / [[UIScreen mainScreen] scale];
-
- [_containerView.layer addSublayer:_borderLayer];
-
_formatter = [[MGLDistanceFormatter alloc] init];
// Image labels are now images
@@ -176,6 +182,7 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
[self addSubview:view];
}
_labelViews = [labelViews copy];
+ _lastLabelWidth = MGLScaleBarLabelWidthHint;
// Zero is a special case (no formatting)
[self addZeroLabel];
@@ -194,16 +201,33 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
#pragma mark - Dimensions
-- (CGSize)intrinsicContentSize {
- return self.actualWidth > 0 ? CGSizeMake(ceil(self.actualWidth + self.lastLabelWidth/2), 16) : CGSizeZero;
+- (void)setBorderWidth:(CGFloat)borderWidth {
+ _borderWidth = borderWidth;
+ _containerView.layer.borderWidth = borderWidth / [[UIScreen mainScreen] scale];
}
+// Determines the width of the bars NOT the size of the entire scale bar,
+// which includes space for (half) a label.
+// Uses the current set `row`
- (CGFloat)actualWidth {
- CGFloat width = self.row.distance / [self unitsPerPoint];
- return !isnan(width) ? width : 0;
+ CGFloat unitsPerPoint = [self unitsPerPoint];
+
+ if (unitsPerPoint == 0.0) {
+ return 0.0;
+ }
+
+ CGFloat width = self.row.distance / unitsPerPoint;
+
+ if (width <= MGLScaleBarMinimumBarWidth) {
+ return 0.0;
+ }
+
+ // Round, so that each bar section has an integer width
+ return self.row.numberOfBars * floor(width/self.row.numberOfBars);
}
- (CGFloat)maximumWidth {
+ // TODO: Consider taking Scale Bar margins into account here.
CGFloat fullWidth = CGRectGetWidth(self.superview.bounds);
return floorf(fullWidth / 2);
}
@@ -215,6 +239,10 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
#pragma mark - Convenience methods
- (BOOL)usesRightToLeftLayout {
+ if (self.testingRightToLeftOverride) {
+ return [self.testingRightToLeftOverride boolValue];
+ }
+
return [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.superview.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft;
}
@@ -265,10 +293,61 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
[self updateVisibility];
+ self.recalculateSize = YES;
+ [self invalidateIntrinsicContentSize];
+}
+
+- (CGSize)intrinsicContentSize {
+ // Size is calculated elsewhere - since intrinsicContentSize is part of the
+ // constraint system, this should be done in updateConstraints
+ if (self.size.width < 0.0) {
+ return CGSizeZero;
+ }
+ return self.size;
+}
+
+/// updateConstraints
+///
+/// The primary job of updateConstraints here is to recalculate the
+/// intrinsicContentSize: _metersPerPoint and the maximum width determine the
+/// current "row", which in turn determines the "actualWidth". To obtain the full
+/// width of the scale bar, we also need to include some space for the "last"
+/// label
+
+- (void)updateConstraints {
+ if (self.isHidden || !self.recalculateSize) {
+ [super updateConstraints];
+ return;
+ }
+
+ // TODO: Improve this (and the side-effects)
self.row = [self preferredRow];
- [self invalidateIntrinsicContentSize];
+ NSAssert(self.row.numberOfBars > 0, @"");
+
+ CGFloat totalBarWidth = self.actualWidth;
+
+ if (totalBarWidth <= 0.0) {
+ [super updateConstraints];
+ return;
+ }
+
+ // Determine the "lastLabelWidth". This has changed to take a maximum of each
+ // label, to ensure that the size does not change in LTR & RTL layouts, and
+ // also to stop jiggling when the scale bar is on the right hand of the screen
+ // This will most likely be a constant, as we take a max using a "hint" for
+ // the initial value
+
+ if (self.shouldLayoutBars) {
+ [self updateLabels];
+ }
+
+ CGFloat halfLabelWidth = ceil(self.lastLabelWidth/2);
+
+ self.size = CGSizeMake(totalBarWidth + halfLabelWidth, 16);
+
[self setNeedsLayout];
+ [super updateConstraints]; // This calls intrinsicContentSize
}
- (void)updateVisibility {
@@ -297,11 +376,9 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
return;
}
+ self.shouldLayoutBars = YES;
+
_row = row;
- [_bars makeObjectsPerformSelector:@selector(removeFromSuperview)];
- _bars = nil;
-
- [self updateLabels];
}
#pragma mark - Views
@@ -378,9 +455,9 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
CLLocationDistance barDistance = multiplier * i;
UIImage *image = [self cachedLabelImageForDistance:barDistance];
- if (i == self.row.numberOfBars) {
- self.lastLabelWidth = image.size.width;
- }
+
+ self.lastLabelWidth = MAX(self.lastLabelWidth, image.size.width);
+
labelView.layer.contents = (id)image.CGImage;
labelView.layer.contentsScale = image.scale;
}
@@ -397,53 +474,83 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
- (void)layoutSubviews {
[super layoutSubviews];
- if (!self.row.numberOfBars) {
- // Current distance is not within allowed range
+ if (!self.recalculateSize) {
return;
}
- [self layoutBars];
- [self layoutLabels];
-}
+ self.recalculateSize = NO;
+
+ // If size is 0, then we keep the existing layout (which will fade out)
+ if (self.size.width <= 0.0) {
+ return;
+ }
+
+ CGFloat totalBarWidth = self.actualWidth;
+
+ if (totalBarWidth <= 0.0) {
+ return;
+ }
+
+ if (self.shouldLayoutBars) {
+ self.shouldLayoutBars = NO;
+ [_bars makeObjectsPerformSelector:@selector(removeFromSuperview)];
+ _bars = nil;
+ }
+
+ // Re-layout the component bars and labels of the scale bar
+ CGFloat intrinsicContentHeight = self.intrinsicContentSize.height;
+ CGFloat barWidth = totalBarWidth/self.bars.count;
-- (void)layoutBars {
- CGFloat barWidth = round((self.intrinsicContentSize.width - self.borderWidth * 2.0f) / self.bars.count);
+ BOOL RTL = [self usesRightToLeftLayout];
+ CGFloat halfLabelWidth = ceil(self.lastLabelWidth/2);
+ CGFloat barOffset = RTL ? halfLabelWidth : 0.0;
+ self.containerView.frame = CGRectMake(barOffset,
+ intrinsicContentHeight - MGLBarHeight,
+ totalBarWidth,
+ MGLBarHeight);
+
+ [self layoutBarsWithWidth:barWidth];
+
+ CGFloat yPosition = round(0.5 * ( intrinsicContentHeight - MGLBarHeight));
+ CGFloat barDelta = RTL ? -barWidth : barWidth;
+ [self layoutLabelsWithOffset:barOffset delta:barDelta yPosition:yPosition];
+}
+
+- (void)layoutBarsWithWidth:(CGFloat)barWidth {
NSUInteger i = 0;
for (UIView *bar in self.bars) {
- CGFloat xPosition = barWidth * i + self.borderWidth;
+ CGFloat xPosition = barWidth * i;
bar.backgroundColor = (i % 2 == 0) ? self.primaryColor : self.secondaryColor;
- bar.frame = CGRectMake(xPosition, self.borderWidth, barWidth, MGLBarHeight);
+ bar.frame = CGRectMake(xPosition, 0, barWidth, MGLBarHeight);
i++;
}
-
- self.containerView.frame = CGRectMake(CGRectGetMinX(self.bars.firstObject.frame),
- self.intrinsicContentSize.height-MGLBarHeight,
- self.actualWidth,
- MGLBarHeight+self.borderWidth*2);
-
- [CATransaction begin];
- [CATransaction setDisableActions:YES];
- self.borderLayer.frame = CGRectInset(self.containerView.bounds, self.borderWidth, self.borderWidth);
- self.borderLayer.zPosition = FLT_MAX;
- [CATransaction commit];
}
-- (void)layoutLabels {
- CGFloat barWidth = round(self.actualWidth / self.bars.count);
- BOOL RTL = [self usesRightToLeftLayout];
- NSUInteger i = RTL ? self.bars.count : 0;
+- (void)layoutLabelsWithOffset:(CGFloat)barOffset delta:(CGFloat)barDelta yPosition:(CGFloat)yPosition {
+#if !defined(NS_BLOCK_ASSERTIONS)
+ NSUInteger countOfVisibleLabels = 0;
+ for (UIView *view in self.labelViews) {
+ if (!view.isHidden) {
+ countOfVisibleLabels++;
+ }
+ }
+ NSAssert(self.bars.count == countOfVisibleLabels - 1, @"");
+#endif
+
+ CGFloat xPosition = barOffset;
+
+ if (barDelta < 0) {
+ xPosition -= (barDelta*self.bars.count);
+ }
+
for (UIView *label in self.labelViews) {
- CGFloat xPosition = round(barWidth * i - CGRectGetMidX(label.bounds) + self.borderWidth);
- CGFloat yPosition = round(0.5 * (self.intrinsicContentSize.height - MGLBarHeight));
-
- CGRect frame = label.frame;
- frame.origin.x = xPosition;
- frame.origin.y = yPosition;
- label.frame = frame;
-
- i = RTL ? i-1 : i+1;
+ // Label frames have 0 size - though the layer contents use "center" and do
+ // not clip to bounds. This way we don't need to worry about positioning the
+ // label. (Though you won't see the label in the view debugger)
+ label.frame = CGRectMake(xPosition, yPosition, 0.0, 0.0);
+
+ xPosition += barDelta;
}
}
-
@end