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

Commit

Permalink
[ios] Annotation view degrees of freedom
Browse files Browse the repository at this point in the history
Replaced MGLAnnotationView’s flat property with a freeAxes property that allows 0–2 degrees of freedom (pitch and/or rotation).

Reformatted and copyedited MGLAnnotationView documentation. Removed the unnecessary custom getter on scalesWithViewingDistance.

Fixes #2528.
  • Loading branch information
1ec5 committed Jun 5, 2016
1 parent 5b64c3a commit 151e677
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 40 deletions.
2 changes: 1 addition & 1 deletion platform/ios/Mapbox.playground/Contents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class MapDelegate: NSObject, MGLMapViewDelegate {
let av = PlaygroundAnnotationView(reuseIdentifier: "annotation")
av.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
av.centerOffset = CGVector(dx: -15, dy: -15)
av.flat = true
av.freeAxes = .X;
let centerView = UIView(frame: CGRectInset(av.bounds, 3, 3))
centerView.backgroundColor = UIColor.whiteColor()
av.addSubview(centerView)
Expand Down
8 changes: 4 additions & 4 deletions platform/ios/app/MBXViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -584,17 +584,17 @@ - (MGLAnnotationView *)mapView:(MGLMapView *)mapView viewForAnnotation:(id<MGLAn
if (!annotationView)
{
annotationView = [[MBXAnnotationView alloc] initWithReuseIdentifier:MBXViewControllerAnnotationViewReuseIdentifer];
annotationView.frame = CGRectMake(0, 0, 10, 10);
annotationView.frame = CGRectMake(0, 0, 30, 30);
annotationView.centerColor = [UIColor whiteColor];

// uncomment to flatten the annotation view against the map when the map is tilted
// uncomment to lay the annotation view flat against the map when the map is tilted
// this currently causes severe performance issues when more than 2k annotations are visible
// annotationView.flat = YES;
// annotationView.freeAxes = MGLAnnotationViewBillboardAxisY;

// uncomment to force annotation view to maintain a constant size when the map is tilted
// by default, annotation views will shrink and grow as the move towards and away from the
// horizon. Relatedly, annotations backed by GL sprites ONLY scale with viewing distance currently.
// annotationView.scalesWithViewingDistance = NO;
// annotationView.scalesWithViewingDistance = NO;

} else {
// orange indicates that the annotation view was reused
Expand Down
1 change: 1 addition & 0 deletions platform/ios/jazzy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ custom_categories:
- MGLAnnotationImage
- MGLAnnotationVerticalAlignment
- MGLAnnotationView
- MGLAnnotationViewBillboardAxis
- MGLCalloutView
- MGLCalloutViewDelegate
- MGLMultiPoint
Expand Down
128 changes: 108 additions & 20 deletions platform/ios/src/MGLAnnotationView.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,143 @@

NS_ASSUME_NONNULL_BEGIN

/** The MGLAnnotationView class is responsible for representing point-based annotation markers as a view. Annotation views represent an annotation object, which is an object that corresponds to the MGLAnnotation protocol. When an annotation’s coordinate point is visible on the map view, the map view delegate is asked to provide a corresponding annotation view. If an annotation view is created with a reuse identifier, the map view may recycle the view when it goes offscreen. */
/**
Options for locking the orientation of an `MGLAnnotationView` along one or more
axes for a billboard effect.
*/
typedef NS_OPTIONS(NSUInteger, MGLAnnotationViewBillboardAxis)
{
/**
Orients the annotation view such that its x-axis is always fixed with
respect to the map.
If this option is unset, the annotation view remains unchanged as the map’s
pitch increases, so that the view appears to stand upright on the tilted
map. If this option is set, the annotation view tilts as the map’s pitch
increases, so that the view appears to lie flat against the tilted map.
For example, you would set this option if the annotation view depicts an
arrow that should always point due south. You would unset this option if
the arrow should always point down towards the ground.
*/
MGLAnnotationViewBillboardAxisX = 0x1 << 0,

/**
Orients the annotation view such that its y-axis is always fixed with
respect to the map.
If this option is unset, the annotation view remains unchanged as the map
is rotated. If this option is set, the annotation view rotates as the map
rotates.
For example, you would set this option if the annotation view should be
aligned with a street, regardless of the direction from which the user
views the street.
*/
MGLAnnotationViewBillboardAxisY = 0x1 << 1,

/**
Orients the annotation view such that its z-axis is always fixed with
respect to the map.
Because `MGLMapView` does not support changes to its bank, or roll, this
option has no effect.
*/
MGLAnnotationViewBillboardAxisZ = 0x1 << 2,

/**
Orients the annotation view such that all three axes are always fixed with
respect to the map.
*/
MGLAnnotationViewBillboardAxisAll = (MGLAnnotationViewBillboardAxisX | MGLAnnotationViewBillboardAxisY | MGLAnnotationViewBillboardAxisZ),
};

/**
The `MGLAnnotationView` class is responsible for representing point-based
annotation markers as a view. Annotation views represent an annotation object,
which is an object that corresponds to the `MGLAnnotation` protocol. When an
annotation’s coordinate point is visible on the map view, the map view delegate
is asked to provide a corresponding annotation view. If an annotation view is
created with a reuse identifier, the map view may recycle the view when it goes
offscreen.
*/
@interface MGLAnnotationView : UIView

/**
Initializes and returns a new annotation view object.
@param reuseIdentifier The string that identifies that this annotation view is reusable.
@param reuseIdentifier The string that identifies that this annotation view is
reusable.
@return The initialized annotation view object.
*/
- (instancetype)initWithReuseIdentifier:(nullable NSString *)reuseIdentifier;

/**
The string that identifies that this annotation view is reusable. (read-only)
The string that identifies that this annotation view is reusable.
You specify the reuse identifier when you create the view. You use the identifier later to retrieve an annotation view that was
created previously but which is currently unused because its annotation is not on screen.
You specify the reuse identifier when you create the view. You use the
identifier later to retrieve an annotation view that was created previously but
which is currently unused because its annotation does not lie within the map
view’s viewport.
If you define distinctly different types of annotations (with distinctly different annotation views to go with them), you can
differentiate between the annotation types by specifying different reuse identifiers for each one.
If you define distinctly different types of annotations (with distinctly
different annotation views to go with them), you can differentiate between the
annotation types by specifying different reuse identifiers for each one.
*/
@property (nonatomic, readonly, nullable) NSString *reuseIdentifier;

/**
Annotation view is centered at the coordinate point of the associated annotation.
The offset from the annotation’s logical center to the annotation view’s visual
center point.
The offset is measured in points. A positive offset moves the annotation view
towards the bottom-right, while a negative offset moves it towards the
top-left.
By changing this property you can reposition the view as needed. The offset is measured in points.
Positive offset moves the annotation view towards the bottom right, while negative offset moves it towards the top left.
By default, there is no offset, so the view is perfectly centered around the
position on-screen that corresponds to the annotation object’s `coordinate`
property.
Set the offset if the annotation view’s visual center point is somewhere other
than the center of the view. For example, the view may contain an image that
depicts a downward-pointing pushpin or thumbtack, with the tip positioned at
the center-bottom of the view. In that case, you would set the offset’s `dx` to
zero and its `dy` to half the height of the view.
*/
@property (nonatomic) CGVector centerOffset;


/**
Setting this property to YES will force the annotation view to tilt according to the associated map view.
An option that specifies the annotation view’s degrees of freedom.
By default, none of the axes are free; in other words, the annotation view is
oriented like a billboard with respect to the x-, y-, and z-axes. See
`MGLAnnotationViewBillboardAxis` for available options.
*/
@property (nonatomic, assign, getter=isFlat) BOOL flat;
@property (nonatomic, assign) MGLAnnotationViewBillboardAxis freeAxes;

/**
Setting this property to YES will cause the annotation view to shrink as it approaches the horizon and grow as it moves away from the
horizon when the associated map view is tilted. Conversely, setting this property to NO will ensure that the annotation view maintains
a constant size even when the map view is tilted. To maintain consistency with annotation representations that are not backed by an
MGLAnnotationView object, the default value of this property is YES.
A Boolean value that determines whether the annotation view’s scale changes as
the distance between the viewpoint and the annotation view changes on a tilted
map.
When the value of this property is `YES` and the map is tilted, the annotation
view appears smaller if it is towards the top of the view (closer to the
horizon) and larger if it is towards the bottom of the view (closer to the
viewpoint). This is also the behavior of `MGLAnnotationImage` objects. When the
value of this property is `NO` or the map’s pitch is zero, the annotation view
remains the same size regardless of its position on-screen.
The default value of this property is `YES`. Set this property to `NO` if the
view’s legibility is important.
*/
@property (nonatomic, assign, getter=isScaledWithViewingDistance) BOOL scalesWithViewingDistance;
@property (nonatomic, assign) BOOL scalesWithViewingDistance;

/**
Called when the view is removed from the reuse queue.
The default implementation of this method does nothing. You can override it in your custom annotation views and use it to put the view
in a known state before it is returned to your map view delegate.
The default implementation of this method does nothing. You can override it in
your custom annotation views and use it to put the view in a known state before
it is returned to your map view delegate.
*/
- (void)prepareForReuse;

Expand Down
21 changes: 10 additions & 11 deletions platform/ios/src/MGLAnnotationView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -45,27 +45,27 @@ - (void)setCenter:(CGPoint)center
super.center = center;
}

- (void)setCenter:(CGPoint)center pitch:(CGFloat)pitch
- (void)setCenter:(CGPoint)center direction:(CLLocationDirection)direction pitch:(CGFloat)pitch
{
self.center = center;

if (pitch >= 0 && self.flat)
CATransform3D t = CATransform3DIdentity;
if (pitch >= 0 && (self.freeAxes & MGLAnnotationViewBillboardAxisX))
{
[self updatePitch:pitch];
t = CATransform3DRotate(t, MGLRadiansFromDegrees(pitch), 1.0, 0, 0);
}
if (direction >= 0 && (self.freeAxes & MGLAnnotationViewBillboardAxisY))
{
t = CATransform3DRotate(t, MGLRadiansFromDegrees(-direction), 0.0, 0.0, 1.0);
}
self.layer.transform = t;

if (self.scalesWithViewingDistance)
{
[self updateScaleForPitch:pitch];
}
}

- (void)updatePitch:(CGFloat)pitch
{
CATransform3D t = CATransform3DRotate(CATransform3DIdentity, MGLRadiansFromDegrees(pitch), 1.0, 0, 0);
self.layer.transform = t;
}

- (void)updateScaleForPitch:(CGFloat)pitch
{
CGFloat superviewHeight = CGRectGetHeight(self.superview.frame);
Expand All @@ -90,8 +90,7 @@ - (void)updateScaleForPitch:(CGFloat)pitch
// reduction is then normalized for a scale of 1.0.
CGFloat pitchAdjustedScale = 1.0 - maxScaleReduction * pitchIntensity;

CATransform3D transform = self.flat ? self.layer.transform : CATransform3DIdentity;
self.layer.transform = CATransform3DScale(transform, pitchAdjustedScale, pitchAdjustedScale, 1);
self.layer.transform = CATransform3DScale(self.layer.transform, pitchAdjustedScale, pitchAdjustedScale, 1);
}
}

Expand Down
2 changes: 1 addition & 1 deletion platform/ios/src/MGLAnnotationView_Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic) id<MGLAnnotation> annotation;
@property (nonatomic, readwrite, nullable) NSString *reuseIdentifier;

- (void)setCenter:(CGPoint)center pitch:(CGFloat)pitch;
- (void)setCenter:(CGPoint)center direction:(CLLocationDirection)direction pitch:(CGFloat)pitch;

@end

Expand Down
9 changes: 6 additions & 3 deletions platform/ios/src/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1786,7 +1786,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
{
// Redundantly move the associated annotation view outside the scope of the animation-less transaction block in -updateAnnotationViews.
CGPoint center = [self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self];
[annotationContext.annotationView setCenter:center pitch:self.camera.pitch];
[annotationContext.annotationView setCenter:center direction:self.direction pitch:self.camera.pitch];
}
else
{
Expand Down Expand Up @@ -4484,6 +4484,9 @@ - (void)updateAnnotationViews
[CATransaction begin];
[CATransaction setDisableActions:YES];

CLLocationDirection direction = self.direction;
CGFloat pitch = self.camera.pitch;

for (auto &pair : _annotationContextsByAnnotationTag)
{
CGRect viewPort = CGRectInset(self.bounds, -_largestAnnotationViewSize.width - MGLAnnotationUpdateViewportOutset.width, -_largestAnnotationViewSize.height - MGLAnnotationUpdateViewportOutset.width);
Expand All @@ -4503,7 +4506,7 @@ - (void)updateAnnotationViews
}

CGPoint center = [self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self];
[annotationView setCenter:center pitch:self.camera.pitch];
[annotationView setCenter:center direction:direction pitch:pitch];

annotationContext.annotationView = annotationView;
}
Expand All @@ -4517,7 +4520,7 @@ - (void)updateAnnotationViews
else
{
CGPoint center = [self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self];
[annotationView setCenter:center pitch:self.camera.pitch];
[annotationView setCenter:center direction:direction pitch:pitch];
}
}

Expand Down

0 comments on commit 151e677

Please sign in to comment.